Changes to allow decoder height to be a 2x multiple of bitcell height.

Convert to use li layer in pgates.
Fix multifinger devices with li layers.
Simplify wordline driver input routing.
Fix power pin direction option update.
PEP8 cleanup
Changes to simplify metal preferred directions and pitches.
Split of control logic tests.
This commit is contained in:
mrg 2020-04-23 14:43:54 -07:00
parent 5666e79287
commit dd73afc983
26 changed files with 770 additions and 540 deletions

View File

@ -41,11 +41,18 @@ class layout():
self.visited = [] # List of modules we have already visited
self.is_library_cell = False # Flag for library cells
self.gds_read()
try:
from tech import power_grid
self.pwr_grid_layer = power_grid[0]
except ImportError:
self.pwr_grid_layer = "m3"
if "li" in techlayer:
self.layer_indices = ["active", "li", "m1", "m2", "m3", "m4"]
else:
self.layer_indices = ["active", "m1", "m2", "m3", "m4"]
############################################################
# GDS layout
@ -462,7 +469,12 @@ class layout():
def add_via(self, layers, offset, size=[1, 1], directions=None, implant_type=None, well_type=None):
""" Add a three layer via structure. """
if not directions:
# Non-preferred directions
if directions == "nonpref":
directions = (self.get_preferred_direction(layers[2]),
self.get_preferred_direction(layers[0]))
# Preferred if not specified
elif not directions or directions == "pref":
directions = (self.get_preferred_direction(layers[0]),
self.get_preferred_direction(layers[2]))
@ -487,7 +499,12 @@ class layout():
accounting for mirroring and rotation.
"""
if not directions:
# Non-preferred directions
if directions == "nonpref":
directions = (self.get_preferred_direction(layers[2]),
self.get_preferred_direction(layers[0]))
# Preferred if not specified
elif not directions or directions == "pref":
directions = (self.get_preferred_direction(layers[0]),
self.get_preferred_direction(layers[2]))
@ -513,36 +530,54 @@ class layout():
return inst
def add_via_stack(self, offset, from_layer, to_layer,
direction=None,
size=[1, 1]):
directions=None,
size=[1, 1],
implant_type=None,
well_type=None):
"""
Punch a stack of vias from a start layer to a target layer.
"""
return self.__add_via_stack_internal(offset=offset,
direction=direction,
directions=directions,
from_layer=from_layer,
to_layer=to_layer,
via_func=self.add_via,
last_via=None,
size=size)
size=size,
implant_type=implant_type,
well_type=well_type)
def add_via_stack_center(self, offset, from_layer, to_layer,
direction=None,
size=[1, 1]):
def get_layer_index(self, layer_name):
"""
Return a layer index from bottom up in this tech.
"""
return self.layer_indices.index(layer_name)
def add_via_stack_center(self,
offset,
from_layer,
to_layer,
directions=None,
size=[1, 1],
implant_type=None,
well_type=None):
"""
Punch a stack of vias from a start layer to a target layer by the center
coordinate accounting for mirroring and rotation.
"""
return self.__add_via_stack_internal(offset=offset,
direction=direction,
directions=directions,
from_layer=from_layer,
to_layer=to_layer,
via_func=self.add_via_center,
last_via=None,
size=size)
size=size,
implant_type=implant_type,
well_type=well_type)
def __add_via_stack_internal(self, offset, direction, from_layer, to_layer,
via_func, last_via, size):
def __add_via_stack_internal(self, offset, directions, from_layer, to_layer,
via_func, last_via, size, implant_type=None, well_type=None):
"""
Punch a stack of vias from a start layer to a target layer. Here we
figure out whether to punch it up or down the stack.
@ -551,8 +586,8 @@ class layout():
if from_layer == to_layer:
return last_via
from_id = int(from_layer[1])
to_id = int(to_layer[1])
from_id = self.get_layer_index(from_layer)
to_id = self.get_layer_index(to_layer)
if from_id < to_id: # grow the stack up
search_id = 0
@ -563,19 +598,36 @@ class layout():
curr_stack = next(filter(lambda stack: stack[search_id] == from_layer, layer_stacks), None)
if curr_stack is None:
raise ValueError("Cannot create via from '{0}' to '{1}'." \
"Layer '{0}' not defined"
.format(from_layer, to_layer))
raise ValueError("Cannot create via from '{0}' to '{1}'."
"Layer '{0}' not defined".format(from_layer, to_layer))
via = via_func(layers=curr_stack, size=size, offset=offset, directions=direction)
return self.__add_via_stack_internal(offset=offset,
direction=direction,
from_layer=curr_stack[next_id],
to_layer=to_layer,
via_func=via_func,
last_via=via,
size=size)
via = via_func(layers=curr_stack,
size=size,
offset=offset,
directions=directions,
implant_type=implant_type,
well_type=well_type)
if from_layer == "active":
via = self.__add_via_stack_internal(offset=offset,
directions=directions,
from_layer=curr_stack[next_id],
to_layer=to_layer,
via_func=via_func,
last_via=via,
size=size,
implant_type=implant_type,
well_type=well_type)
else:
via = self.__add_via_stack_internal(offset=offset,
directions=directions,
from_layer=curr_stack[next_id],
to_layer=to_layer,
via_func=via_func,
last_via=via,
size=size)
return via
def add_ptx(self, offset, mirror="R0", rotate=0, width=1, mults=1, tx_type="nmos"):
"""Adds a ptx module to the design."""
import ptx
@ -1200,27 +1252,18 @@ class layout():
"supply router."
.format(name, inst.name, self.pwr_grid_layer))
def add_power_pin(self, name, loc, size=[1, 1], vertical=False, start_layer="m1"):
def add_power_pin(self, name, loc, size=[1, 1], directions=None, start_layer="m1"):
"""
Add a single power pin from the lowest power_grid layer down to M1 (or li) at
the given center location. The starting layer is specified to determine
which vias are needed.
"""
# Force vdd/gnd via stack to be vertically or horizontally oriented
# Default: None, uses prefered metal directions
if vertical:
direction = ("V", "V")
elif not vertical and vertical is not None:
direction = ("H", "H")
else:
direction = None
via = self.add_via_stack_center(from_layer=start_layer,
to_layer=self.pwr_grid_layer,
size=size,
offset=loc,
direction=direction)
directions=directions)
if start_layer == self.pwr_grid_layer:
self.add_layout_pin_rect_center(text=name,
layer=self.pwr_grid_layer,

View File

@ -104,9 +104,9 @@ class bitcell_base_array(design.design):
# For non-square via stacks, vertical/horizontal direction refers to the stack orientation in 2d space
# Default uses prefered directions for each layer; this cell property is only currently used by s8 tech (03/20)
try:
force_power_pins_vertical = cell_properties.bitcell_force_power_pins_vertical
bitcell_power_pin_directions = cell_properties.bitcell_power_pin_directions
except AttributeError:
force_power_pins_vertical = None
bitcell_power_pin_directions = None
# Add vdd/gnd via stacks
for row in range(self.row_size):
@ -114,7 +114,10 @@ class bitcell_base_array(design.design):
inst = self.cell_inst[row,col]
for pin_name in ["vdd", "gnd"]:
for pin in inst.get_pins(pin_name):
self.add_power_pin(name=pin_name, loc=pin.center(), vertical=force_power_pins_vertical, start_layer=pin.layer)
self.add_power_pin(name=pin_name,
loc=pin.center(),
directions=bitcell_power_pin_directions,
start_layer=pin.layer)
def _adjust_x_offset(self, xoffset, col, col_offset):
tempx = xoffset

View File

@ -62,7 +62,7 @@ class delay_chain(design.design):
self.add_pin("gnd", "GROUND")
def add_modules(self):
self.inv = factory.create(module_type="pinv", route_output=False)
self.inv = factory.create(module_type="pinv")
self.add_mod(self.inv)
def create_inverters(self):

View File

@ -186,16 +186,25 @@ class hierarchical_decoder(design.design):
self.num_rows = math.ceil(self.num_outputs / self.cell_multiple)
# We will place this many final decoders per row
self.decoders_per_row = math.ceil(self.num_outputs / self.num_rows)
# Calculates height and width of row-decoder
if (self.num_inputs == 4 or self.num_inputs == 5):
nand_width = self.and2.width
nand_inputs = 2
else:
nand_width = self.and3.width
self.internal_routing_width = self.m2_pitch * (self.total_number_of_predecoder_outputs + 1)
nand_inputs = 3
self.internal_routing_width = self.m3_pitch * (self.total_number_of_predecoder_outputs + 1)
self.row_decoder_height = self.inv.height * self.num_rows
decoder_input_wire_height = self.decoders_per_row * nand_inputs * self.m3_pitch
#print(self.decoders_per_row, nand_inputs)
#print(decoder_input_wire_height, self.cell_height)
if decoder_input_wire_height > self.cell_height:
debug.warning("Cannot fit multi-bit decoder routes per row.")
#debug.check(decoder_input_wire_height < self.cell_height, "Cannot fit multi-bit decoder routes per row.")
self.input_routing_width = (self.num_inputs + 1) * self.m2_pitch
self.input_routing_width = (self.num_inputs + 1) * self.m3_pitch
# Calculates height and width of hierarchical decoder
# Add extra pitch for good measure
self.height = max(self.predecoder_height, self.row_decoder_height) + self.m3_pitch
@ -217,8 +226,8 @@ class hierarchical_decoder(design.design):
input_offset=vector(min_x - self.input_routing_width, 0)
input_bus_names = ["addr_{0}".format(i) for i in range(self.num_inputs)]
self.input_bus = self.create_vertical_pin_bus(layer="m2",
pitch=self.m2_pitch,
self.input_bus = self.create_vertical_pin_bus(layer="m3",
pitch=self.m3_pitch,
offset=input_offset,
names=input_bus_names,
length=input_height)
@ -238,7 +247,7 @@ class hierarchical_decoder(design.design):
# To prevent conflicts, we will offset each input connect so
# that it aligns with the vdd/gnd rails
decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * self.inv.height)
decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * (self.inv.height + self.m1_pitch))
input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1)
self.route_input_bus(decoder_offset, input_offset)
@ -254,7 +263,7 @@ class hierarchical_decoder(design.design):
# To prevent conflicts, we will offset each input connect so
# that it aligns with the vdd/gnd rails
decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * self.inv.height)
decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * (self.inv.height + self.m1_pitch))
input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1)
self.route_input_bus(decoder_offset, input_offset)
@ -265,11 +274,15 @@ class hierarchical_decoder(design.design):
vertical M2 coordinate to the predecode inputs
"""
self.add_via_center(layers=self.m2_stack,
offset=input_offset)
self.add_via_center(layers=self.m2_stack,
offset=output_offset)
self.add_path(("m3"), [input_offset, output_offset])
self.add_via_stack_center(from_layer="m2",
to_layer="m3",
offset=input_offset,
directions="nonpref")
self.add_via_stack_center(from_layer="m2",
to_layer="m3",
offset=output_offset,
directions="nonpref")
self.add_path(("m2"), [input_offset, output_offset])
def add_pins(self):
""" Add the module pins """
@ -467,8 +480,8 @@ class hierarchical_decoder(design.design):
if (self.num_inputs >= 4):
# This leaves an offset for the predecoder output jogs
input_bus_names = ["predecode_{0}".format(i) for i in range(self.total_number_of_predecoder_outputs)]
self.predecode_bus = self.create_vertical_pin_bus(layer="m2",
pitch=self.m2_pitch,
self.predecode_bus = self.create_vertical_pin_bus(layer="m3",
pitch=self.m3_pitch,
offset=vector(0, 0),
names=input_bus_names,
length=self.height)
@ -538,7 +551,7 @@ class hierarchical_decoder(design.design):
if (output_index < self.num_outputs):
row_index = math.floor(output_index / self.decoders_per_row)
row_remainder = (output_index % self.decoders_per_row)
row_offset = row_index * self.and_inst[0].height + (3 * row_remainder + 1) * self.m3_pitch
row_offset = row_index * self.and_inst[0].height + (3 * row_remainder + 1) * self.m2_pitch
predecode_name = "predecode_{}".format(index_A)
self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("A"),
@ -546,11 +559,11 @@ class hierarchical_decoder(design.design):
predecode_name = "predecode_{}".format(index_B)
self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("B"),
row_offset + self.m3_pitch)
row_offset + self.m2_pitch)
predecode_name = "predecode_{}".format(index_C)
self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("C"),
row_offset + 2 * self.m3_pitch)
row_offset + 2 * self.m2_pitch)
output_index = output_index + 1
def route_vdd_gnd(self):
@ -590,18 +603,26 @@ class hierarchical_decoder(design.design):
# If we have a single decoder per row, we can route on M1
if self.decoders_per_row == 1:
rail_pos = vector(self.predecode_bus[rail_name].x, pin_pos.y)
self.add_path("m1", [rail_pos, pin_pos])
self.add_via_center(layers=self.m1_stack,
offset=rail_pos)
self.add_path("m2", [rail_pos, pin_pos])
self.add_via_stack_center(from_layer=pin.layer,
to_layer="m2",
offset=pin_pos,
directions=("H", "H"))
self.add_via_stack_center(from_layer="m2",
to_layer="m3",
offset=rail_pos,
directions="nonpref")
# If not, we must route over the decoder cells on M3
else:
rail_pos = vector(self.predecode_bus[rail_name].x, y_offset)
mid_pos = vector(pin_pos.x, rail_pos.y)
self.add_wire(self.m2_stack[::-1], [rail_pos, mid_pos, pin_pos])
self.add_wire(self.m2_stack, [rail_pos, mid_pos, pin_pos])
self.add_via_center(layers=self.m2_stack,
offset=rail_pos)
self.add_via_center(layers=self.m1_stack,
offset=pin_pos)
self.add_via_stack_center(from_layer=pin_pos.layer,
to_layer="m3",
offset=pin_pos,
directions="nonpref")
def route_predecode_bus_inputs(self, rail_name, pin, x_offset):
"""
@ -614,9 +635,11 @@ class hierarchical_decoder(design.design):
mid_point1 = vector(x_offset, pin_pos.y)
mid_point2 = vector(x_offset, pin_pos.y + self.inv.height / 2)
rail_pos = vector(self.predecode_bus[rail_name].x, mid_point2.y)
self.add_wire(self.m1_stack, [pin_pos, mid_point1, mid_point2, rail_pos])
self.add_via_center(layers=self.m1_stack,
offset=rail_pos)
self.add_path("m1", [pin_pos, mid_point1, mid_point2, rail_pos])
self.add_via_stack_center(from_layer="m1",
to_layer="m3",
offset=rail_pos,
directions="nonpref")
def input_load(self):
if self.determine_predecodes(self.num_inputs)[1]==0:

View File

@ -57,10 +57,10 @@ class hierarchical_predecode(design.design):
self.height = self.number_of_outputs * self.and_mod.height
# x offset for input inverters
self.x_off_inv_1 = self.number_of_inputs*self.m2_pitch
self.x_off_inv_1 = self.number_of_inputs*self.m3_pitch
# x offset to AND decoder includes the left rails, mid rails and inverters, plus two extra m2 pitches
self.x_off_and = self.x_off_inv_1 + self.inv.width + (2*self.number_of_inputs + 2) * self.m2_pitch
self.x_off_and = self.x_off_inv_1 + self.inv.width + (2*self.number_of_inputs + 2) * self.m3_pitch
# x offset to output inverters
self.width = self.x_off_and + self.and_mod.width
@ -68,9 +68,9 @@ class hierarchical_predecode(design.design):
def route_rails(self):
""" Create all of the rails for the inputs and vdd/gnd/inputs_bar/inputs """
input_names = ["in_{}".format(x) for x in range(self.number_of_inputs)]
offset = vector(0.5 * self.m2_width, self.m1_pitch)
self.input_rails = self.create_vertical_pin_bus(layer="m2",
pitch=self.m2_pitch,
offset = vector(0.5 * self.m3_width, self.m1_pitch)
self.input_rails = self.create_vertical_pin_bus(layer="m3",
pitch=self.m3_pitch,
offset=offset,
names=input_names,
length=self.height - 2 * self.m1_pitch)
@ -78,9 +78,9 @@ class hierarchical_predecode(design.design):
invert_names = ["Abar_{}".format(x) for x in range(self.number_of_inputs)]
non_invert_names = ["A_{}".format(x) for x in range(self.number_of_inputs)]
decode_names = invert_names + non_invert_names
offset = vector(self.x_off_inv_1 + self.inv.width + 2 * self.m2_pitch, self.m1_pitch)
self.decode_rails = self.create_vertical_bus(layer="m2",
pitch=self.m2_pitch,
offset = vector(self.x_off_inv_1 + self.inv.width + 2 * self.m3_pitch, self.m1_pitch)
self.decode_rails = self.create_vertical_bus(layer="m3",
pitch=self.m3_pitch,
offset=offset,
names=decode_names,
length=self.height - 2 * self.m1_pitch)
@ -146,15 +146,15 @@ class hierarchical_predecode(design.design):
# route one signal next to each vdd/gnd rail since this is
# typically where the p/n devices are and there are no
# pins in the and gates.
y_offset = (num + self.number_of_inputs) * self.inv.height + contact.m1_via.width + self.m1_space
y_offset = (num + self.number_of_inputs) * self.inv.height + contact.m2_via.width + self.m2_space
in_pin = "in_{}".format(num)
a_pin = "A_{}".format(num)
in_pos = vector(self.input_rails[in_pin].x, y_offset)
a_pos = vector(self.decode_rails[a_pin].x, y_offset)
self.add_path("m1", [in_pos, a_pos])
self.add_via_center(layers=self.m1_stack,
self.add_path("m2", [in_pos, a_pos])
self.add_via_center(layers=self.m2_stack,
offset=[self.input_rails[in_pin].x, y_offset])
self.add_via_center(layers=self.m1_stack,
self.add_via_center(layers=self.m2_stack,
offset=[self.decode_rails[a_pin].x, y_offset])
def route_output_and(self):
@ -165,36 +165,43 @@ class hierarchical_predecode(design.design):
z_pin = self.and_inst[num].get_pin("Z")
self.add_layout_pin(text="out_{}".format(num),
layer="m1",
layer=z_pin.layer,
offset=z_pin.ll(),
height=z_pin.height(),
width=z_pin.width())
def route_input_inverters(self):
"""
Route all conections of the inputs inverters [Inputs, outputs, vdd, gnd]
Route all conections of the inputs inverters [Inputs, outputs, vdd, gnd]
"""
for inv_num in range(self.number_of_inputs):
out_pin = "Abar_{}".format(inv_num)
in_pin = "in_{}".format(inv_num)
#add output so that it is just below the vdd or gnd rail
# add output so that it is just below the vdd or gnd rail
# since this is where the p/n devices are and there are no
# pins in the and gates.
y_offset = (inv_num+1) * self.inv.height - 3*self.m1_space
inv_out_pos = self.in_inst[inv_num].get_pin("Z").rc()
right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").lx(),0)
rail_pos = vector(self.decode_rails[out_pin].x,y_offset)
self.add_path("m1", [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos])
self.add_via_center(layers = self.m1_stack,
offset=rail_pos)
y_offset = (inv_num + 1) * self.inv.height - 3 * self.m1_space
inv_out_pin = self.in_inst[inv_num].get_pin("Z")
inv_out_pos = inv_out_pin.rc()
right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").lx(), 0)
rail_pos = vector(self.decode_rails[out_pin].x, y_offset)
self.add_path(inv_out_pin.layer, [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos])
self.add_via_stack_center(from_layer=inv_out_pin.layer,
to_layer="m3",
offset=rail_pos,
directions="nonpref")
#route input
inv_in_pos = self.in_inst[inv_num].get_pin("A").lc()
in_pos = vector(self.input_rails[in_pin].x,inv_in_pos.y)
self.add_path("m1", [in_pos, inv_in_pos])
self.add_via_center(layers=self.m1_stack,
# route input
pin = self.in_inst[inv_num].get_pin("A")
inv_in_pos = pin.lc()
in_pos = vector(self.input_rails[in_pin].x, inv_in_pos.y)
self.add_path("m2", [in_pos, inv_in_pos])
self.add_via_stack_center(from_layer=pin.layer,
to_layer="m2",
offset=inv_in_pos,
directions="nonpref")
self.add_via_center(layers=self.m2_stack,
offset=in_pos)
def route_and_to_rails(self):
@ -205,17 +212,23 @@ class hierarchical_predecode(design.design):
index_lst= and_input_line_combination[k]
if self.number_of_inputs == 2:
gate_lst = ["A","B"]
gate_lst = ["A", "B"]
else:
gate_lst = ["A","B","C"]
gate_lst = ["A", "B", "C"]
# this will connect pins A,B or A,B,C
for rail_pin,gate_pin in zip(index_lst,gate_lst):
pin_pos = self.and_inst[k].get_pin(gate_pin).lc()
for rail_pin, gate_pin in zip(index_lst, gate_lst):
pin = self.and_inst[k].get_pin(gate_pin)
pin_pos = pin.center()
rail_pos = vector(self.decode_rails[rail_pin].x, pin_pos.y)
self.add_path("m1", [rail_pos, pin_pos])
self.add_via_center(layers=self.m1_stack,
offset=rail_pos)
self.add_path("m2", [rail_pos, pin_pos])
self.add_via_center(layers=self.m2_stack,
offset=rail_pos,
directions="nonpref")
self.add_via_stack_center(from_layer=pin.layer,
to_layer="m2",
offset=pin_pos,
directions=("H", "H"))
def route_vdd_gnd(self):
""" Add a pin for each row of vdd/gnd which are must-connects next level up. """

View File

@ -26,8 +26,8 @@ class wordline_driver(design.design):
debug.info(1, "Creating {0}".format(self.name))
self.add_comment("rows: {0} cols: {1}".format(rows, cols))
self.rows = rows
self.cols = cols
self.bitcell_rows = rows
self.bitcell_cols = cols
b = factory.create(module_type="bitcell")
try:
@ -36,6 +36,11 @@ class wordline_driver(design.design):
self.cell_multiple = 1
self.cell_height = self.cell_multiple * b.height
# We may have more than one bitcell per decoder row
self.num_rows = math.ceil(self.bitcell_rows / self.cell_multiple)
# We will place this many final decoders per row
self.decoders_per_row = math.ceil(self.bitcell_rows / self.num_rows)
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
@ -56,10 +61,10 @@ class wordline_driver(design.design):
def add_pins(self):
# inputs to wordline_driver.
for i in range(self.rows):
for i in range(self.bitcell_rows):
self.add_pin("in_{0}".format(i), "INPUT")
# Outputs from wordline_driver.
for i in range(self.rows):
for i in range(self.bitcell_rows):
self.add_pin("wl_{0}".format(i), "OUTPUT")
self.add_pin("en", "INPUT")
self.add_pin("vdd", "POWER")
@ -68,7 +73,7 @@ class wordline_driver(design.design):
def add_modules(self):
self.and2 = factory.create(module_type="pand2",
height=self.cell_height,
size=self.cols)
size=self.bitcell_cols)
self.add_mod(self.and2)
def route_vdd_gnd(self):
@ -79,7 +84,7 @@ class wordline_driver(design.design):
# Find the x offsets for where the vias/pins should be placed
xoffset_list = [self.and_inst[0].lx()]
for num in range(self.rows):
for num in range(self.bitcell_rows):
# this will result in duplicate polygons for rails, but who cares
# use the inverter offset even though it will be the and's too
@ -97,32 +102,32 @@ class wordline_driver(design.design):
def create_drivers(self):
self.and_inst = []
for row in range(self.rows):
for row in range(self.bitcell_rows):
name_and = "wl_driver_and{}".format(row)
# add and2
self.and_inst.append(self.add_inst(name=name_and,
mod=self.and2))
self.connect_inst(["en",
"in_{0}".format(row),
self.connect_inst(["in_{0}".format(row),
"en",
"wl_{0}".format(row),
"vdd", "gnd"])
def setup_layout_constants(self):
# We may have more than one bitcell per decoder row
self.num_rows = math.ceil(self.rows / self.cell_multiple)
self.driver_rows = math.ceil(self.bitcell_rows / self.cell_multiple)
# We will place this many final decoders per row
self.decoders_per_row = math.ceil(self.rows / self.num_rows)
self.decoders_per_row = math.ceil(self.bitcell_rows / self.driver_rows)
def place_drivers(self):
and2_xoffset = 2 * self.m1_width + 5 * self.m1_space
self.width = and2_xoffset + self.and2.width
self.height = self.and2.height * self.num_rows
self.width = and2_xoffset + self.decoders_per_row * self.and2.width
self.height = self.and2.height * self.driver_rows
for row in range(self.rows):
#row = math.floor(inst_index / self.decoders_per_row)
#dec = inst_index % self.decoders_per_row
for inst_index in range(self.bitcell_rows):
row = math.floor(inst_index / self.decoders_per_row)
dec = inst_index % self.decoders_per_row
if (row % 2):
y_offset = self.and2.height * (row + 1)
@ -131,12 +136,12 @@ class wordline_driver(design.design):
y_offset = self.and2.height * row
inst_mirror = "R0"
# x_off = self.internal_routing_width + dec * and_mod.width
and2_offset = [and2_xoffset, y_offset]
x_offset = and2_xoffset + dec * self.and2.width
and2_offset = [x_offset, y_offset]
# add and2
self.and_inst[row].place(offset=and2_offset,
mirror=inst_mirror)
self.and_inst[inst_index].place(offset=and2_offset,
mirror=inst_mirror)
def route_layout(self):
""" Route all of the signals """
@ -149,45 +154,30 @@ class wordline_driver(design.design):
width=self.m2_width,
height=self.height)
for row in range(self.rows):
and_inst = self.and_inst[row]
for inst_index in range(self.bitcell_rows):
and_inst = self.and_inst[inst_index]
row = math.floor(inst_index / self.decoders_per_row)
dec = inst_index % self.decoders_per_row
# en connection
a_pin = and_inst.get_pin("A")
a_pos = a_pin.lc()
clk_offset = vector(en_pin.bc().x, a_pos.y)
self.add_segment_center(layer="m1",
start=clk_offset,
end=a_pos)
self.add_via_center(layers=self.m1_stack,
offset=clk_offset)
# connect the decoder input pin to and2 B
b_pin = and_inst.get_pin("B")
b_pos = b_pin.lc()
# needs to move down since B and input is
# nearly aligned with A inv input
up_or_down = self.m2_space if row % 2 else -self.m2_space
input_offset = vector(0, b_pos.y + up_or_down)
base_offset = vector(clk_offset.x, input_offset.y)
contact_offset = vector(0.5 * self.m2_width + self.m2_space + 0.5 * contact.m1_via.width, 0)
mid_via_offset = base_offset + contact_offset
b_pos = b_pin.center()
clk_offset = vector(en_pin.bc().x, b_pos.y)
self.add_segment_center(layer="m2",
start=clk_offset,
end=b_pos)
self.add_via_center(layers=self.m1_stack,
offset=b_pos)
# connect the decoder input pin to and2 A
a_pin = and_inst.get_pin("A")
a_pos = a_pin.center()
a_offset = vector(clk_offset.x, a_pos.y)
# must under the clk line in M1
self.add_layout_pin_segment_center(text="in_{0}".format(row),
layer="m1",
start=input_offset,
end=mid_via_offset)
self.add_via_center(layers=self.m1_stack,
offset=mid_via_offset,
directions=("V", "V"))
# now connect to the and2 B
self.add_path("m2", [mid_via_offset, b_pos])
contact_offset = b_pos - vector(0.5 * contact.m1_via.height, 0)
self.add_via_center(layers=self.m1_stack,
offset=contact_offset,
directions=("H", "H"))
start=vector(0, a_pos.y),
end=a_pos)
# output each WL on the right
wl_offset = and_inst.get_pin("Z").rc()

View File

@ -76,7 +76,7 @@ class pand2(pgate.pgate):
a2_pin = self.inv_inst.get_pin("A")
mid1_point = vector(0.5 * (z1_pin.cx() + a2_pin.cx()), z1_pin.cy())
mid2_point = vector(mid1_point, a2_pin.cy())
self.add_path("m1",
self.add_path(self.route_layer,
[z1_pin.center(), mid1_point, mid2_point, a2_pin.center()])
def add_layout_pins(self):

View File

@ -77,7 +77,7 @@ class pand3(pgate.pgate):
a2_pin = self.inv_inst.get_pin("A")
mid1_point = vector(0.5 * (z1_pin.cx()+a2_pin.cx()), z1_pin.cy())
mid2_point = vector(mid1_point, a2_pin.cy())
self.add_path("m1",
self.add_path(z1_pin.layer,
[z1_pin.center(), mid1_point, mid2_point, a2_pin.center()])
def add_layout_pins(self):

View File

@ -141,7 +141,7 @@ class pdriver(pgate.pgate):
z_inst_list.append(self.inv_inst_list[x].get_pin("Z"))
a_inst_list.append(self.inv_inst_list[x + 1].get_pin("A"))
mid_point = vector(z_inst_list[x].cx(), a_inst_list[x].cy())
self.add_path("m1",
self.add_path(self.route_layer,
[z_inst_list[x].center(), mid_point,
a_inst_list[x].center()])

View File

@ -33,6 +33,14 @@ class pgate(design.design):
# By default, we make it 10 M1 pitch tall
self.height = 10*self.m1_pitch
if "li" in layer:
self.route_layer = "li"
else:
self.route_layer = "m1"
self.route_layer_width = getattr(self, "{}_width".format(self.route_layer))
self.route_layer_space = getattr(self, "{}_space".format(self.route_layer))
self.route_layer_pitch = getattr(self, "{}_pitch".format(self.route_layer))
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
@ -47,22 +55,21 @@ class pgate(design.design):
""" Pure virtual function """
debug.error("Must over-ride create_layout.", -1)
def connect_pin_to_rail(self, inst, pin, supply):
def connect_pin_to_rail(self, inst, pin_name, supply_name):
""" Connects a ptx pin to a supply rail. """
source_pin = inst.get_pin(pin)
supply_pin = self.get_pin(supply)
if supply_pin.overlaps(source_pin):
return
if supply == "gnd":
height = supply_pin.by() - source_pin.by()
elif supply == "vdd":
height = supply_pin.uy() - source_pin.by()
else:
debug.error("Invalid supply name.", -1)
supply_pin = self.get_pin(supply_name)
if abs(height) > 0:
self.add_rect(layer="m1",
source_pins = inst.get_pins(pin_name)
for source_pin in source_pins:
if supply_name == "gnd":
height = supply_pin.by() - source_pin.by()
elif supply_name == "vdd":
height = supply_pin.uy() - source_pin.by()
else:
debug.error("Invalid supply name.", -1)
self.add_rect(layer=source_pin.layer,
offset=source_pin.ll(),
height=height,
width=source_pin.width())
@ -346,4 +353,4 @@ class pgate(design.design):
return(scaled_bins)
def bin_accuracy(self, ideal_width, width):
return abs(1-(ideal_width - width)/ideal_width)
return abs(1-(ideal_width - width)/ideal_width)

View File

@ -22,17 +22,17 @@ from errors import drc_error
if(OPTS.tech_name == "s8"):
from tech import nmos_bins, pmos_bins, accuracy_requirement
class pinv(pgate.pgate):
"""
Pinv generates gds of a parametrically sized inverter. The
size is specified as the drive size (relative to minimum NMOS) and
a beta value for choosing the pmos size. The inverter's cell
height is usually the same as the 6t library cell and is measured
from center of rail to rail.. The route_output will route the
output to the right side of the cell for easier access.
from center of rail to rail.
"""
def __init__(self, name, size=1, beta=parameter["beta"], height=None, route_output=True):
def __init__(self, name, size=1, beta=parameter["beta"], height=None):
debug.info(2,
"creating pinv structure {0} with size of {1}".format(name,
@ -43,7 +43,6 @@ class pinv(pgate.pgate):
self.nmos_size = size
self.pmos_size = beta * size
self.beta = beta
self.route_output = False
pgate.pgate.__init__(self, name, height)
@ -202,16 +201,20 @@ class pinv(pgate.pgate):
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_source_contact="m1",
add_drain_contact=self.route_layer,
connect_poly=True,
connect_active=True)
connect_drain_active=True)
self.add_mod(self.nmos)
self.pmos = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
add_source_contact="m1",
add_drain_contact=self.route_layer,
connect_poly=True,
connect_active=True)
connect_drain_active=True)
self.add_mod(self.pmos)
def route_supply_rails(self):
@ -220,7 +223,7 @@ class pinv(pgate.pgate):
layer="m1",
offset=vector(0.5 * self.width, 0),
width=self.width)
self.add_layout_pin_rect_center(text="vdd",
layer="m1",
offset=vector(0.5 * self.width, self.height),
@ -266,7 +269,7 @@ class pinv(pgate.pgate):
Route the output (drains) together.
Optionally, routes output to edge.
"""
# Get the drain pins
nmos_drain_pin = self.nmos_inst.get_pin("D")
pmos_drain_pin = self.pmos_inst.get_pin("D")
@ -274,24 +277,16 @@ class pinv(pgate.pgate):
# Pick point at right most of NMOS and connect down to PMOS
nmos_drain_pos = nmos_drain_pin.bc()
pmos_drain_pos = vector(nmos_drain_pos.x, pmos_drain_pin.uc().y)
self.add_path("m1", [nmos_drain_pos, pmos_drain_pos])
self.add_path(self.route_layer, [nmos_drain_pos, pmos_drain_pos])
# Remember the mid for the output
mid_drain_offset = vector(nmos_drain_pos.x, self.output_pos.y)
if self.route_output:
# This extends the output to the edge of the cell
output_offset = mid_drain_offset.scale(0, 1) + vector(self.width, 0)
self.add_layout_pin_segment_center(text="Z",
layer="m1",
start=mid_drain_offset,
end=output_offset)
else:
# This leaves the output as an internal pin (min sized)
self.add_layout_pin_rect_center(text="Z",
layer="m1",
offset=mid_drain_offset \
+ vector(0.5 * self.m1_width, 0))
# This leaves the output as an internal pin (min sized)
output_offset = mid_drain_offset + vector(0.5 * self.route_layer_width, 0)
self.add_layout_pin_rect_center(text="Z",
layer=self.route_layer,
offset=output_offset)
def add_well_contacts(self):
""" Add n/p well taps to the layout and connect to supplies """

View File

@ -72,32 +72,38 @@ class pnand2(pgate.pgate):
def add_ptx(self):
""" Create the PMOS and NMOS transistors. """
self.nmos_nd = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_drain_contact=False,
connect_poly=True,
connect_active=True)
self.add_mod(self.nmos_nd)
self.nmos_left = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_source_contact="m1",
add_drain_contact="active")
self.add_mod(self.nmos_left)
self.nmos_ns = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_source_contact=False,
connect_poly=True,
connect_active=True)
self.add_mod(self.nmos_ns)
self.nmos_right = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_source_contact="active",
add_drain_contact=self.route_layer)
self.add_mod(self.nmos_right)
self.pmos = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
connect_poly=True,
connect_active=True)
self.add_mod(self.pmos)
self.pmos_left = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
add_source_contact="m1",
add_drain_contact=self.route_layer)
self.add_mod(self.pmos_left)
self.pmos_right = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
add_source_contact=self.route_layer,
add_drain_contact="m1")
self.add_mod(self.pmos_right)
def setup_layout_constants(self):
""" Pre-compute some handy layout parameters. """
@ -111,11 +117,11 @@ class pnand2(pgate.pgate):
# Compute the other pmos2 location,
# but determining offset to overlap the
# source and drain pins
self.overlap_offset = self.pmos.get_pin("D").ll() - self.pmos.get_pin("S").ll()
self.overlap_offset = self.pmos_left.get_pin("D").center() - self.pmos_left.get_pin("S").center()
# This is the extra space needed to ensure DRC rules
# to the active contacts
extra_contact_space = max(-self.nmos_nd.get_pin("D").by(), 0)
extra_contact_space = max(-self.nmos_left.get_pin("D").by(), 0)
# This is a poly-to-poly of a flipped cell
self.top_bottom_space = max(0.5 * self.m1_width + self.m1_space + extra_contact_space,
self.poly_extend_active + self.poly_space)
@ -138,19 +144,19 @@ class pnand2(pgate.pgate):
"""
self.pmos1_inst = self.add_inst(name="pnand2_pmos1",
mod=self.pmos)
mod=self.pmos_left)
self.connect_inst(["vdd", "A", "Z", "vdd"])
self.pmos2_inst = self.add_inst(name="pnand2_pmos2",
mod=self.pmos)
mod=self.pmos_right)
self.connect_inst(["Z", "B", "vdd", "vdd"])
self.nmos1_inst = self.add_inst(name="pnand2_nmos1",
mod=self.nmos_nd)
mod=self.nmos_left)
self.connect_inst(["Z", "B", "net1", "gnd"])
self.nmos2_inst = self.add_inst(name="pnand2_nmos2",
mod=self.nmos_ns)
mod=self.nmos_right)
self.connect_inst(["net1", "A", "gnd", "gnd"])
def place_ptx(self):
@ -159,35 +165,29 @@ class pnand2(pgate.pgate):
to provide maximum routing in channel
"""
pmos1_pos = vector(self.pmos.active_offset.x,
self.height - self.pmos.active_height \
pmos1_pos = vector(self.pmos_left.active_offset.x,
self.height - self.pmos_left.active_height \
- self.top_bottom_space)
self.pmos1_inst.place(pmos1_pos)
self.pmos2_pos = pmos1_pos + self.overlap_offset
self.pmos2_inst.place(self.pmos2_pos)
nmos1_pos = vector(self.pmos.active_offset.x,
nmos1_pos = vector(self.pmos_left.active_offset.x,
self.top_bottom_space)
self.nmos1_inst.place(nmos1_pos)
self.nmos2_pos = nmos1_pos + self.overlap_offset
self.nmos2_inst.place(self.nmos2_pos)
# Output position will be in between the PMOS and NMOS
self.output_pos = vector(0,
0.5 * (pmos1_pos.y + nmos1_pos.y + self.nmos_nd.active_height))
def add_well_contacts(self):
"""
Add n/p well taps to the layout and connect to supplies
AFTER the wells are created
"""
self.add_nwell_contact(self.pmos,
self.pmos2_pos + vector(self.m1_pitch, 0))
self.add_pwell_contact(self.nmos_nd,
self.nmos2_pos + vector(self.m1_pitch, 0))
self.add_nwell_contact(self.pmos_right, self.pmos2_pos)
self.add_pwell_contact(self.nmos_left, self.nmos2_pos)
def connect_rails(self):
""" Connect the nmos and pmos to its respective power rails """
@ -200,16 +200,17 @@ class pnand2(pgate.pgate):
def route_inputs(self):
""" Route the A and B inputs """
inputB_yoffset = self.nmos2_inst.uy() + 0.5 * contact.poly_contact.height
# This will help with the wells and the input/output placement
inputB_yoffset = self.pmos2_inst.by() - max(self.poly_extend_active + contact.poly_contact.height,
self.m1_space + 0.5 * contact.m1_via.height)
self.route_input_gate(self.pmos2_inst,
self.nmos2_inst,
inputB_yoffset,
"B",
position="center")
position="right")
# This will help with the wells and the input/output placement
self.inputA_yoffset = self.pmos2_inst.by() - self.poly_extend_active \
- contact.poly_contact.height
self.inputA_yoffset = self.nmos2_inst.uy() + max(self.poly_extend_active + contact.poly_contact.height,
self.m1_space + 0.5 * contact.m1_via.height)
self.route_input_gate(self.pmos1_inst,
self.nmos1_inst,
self.inputA_yoffset,
@ -226,8 +227,7 @@ class pnand2(pgate.pgate):
bottom_pin_offset = nmos_pin.center()
# Output pin
c_pin = self.get_pin("B")
out_offset = vector(c_pin.cx() + self.m1_pitch,
out_offset = vector(nmos_pin.cx(),
self.inputA_yoffset)
# This routes on M2
@ -251,27 +251,17 @@ class pnand2(pgate.pgate):
# [top_pin_offset, mid1_offset, out_offset,
# mid2_offset, bottom_pin_offset])
# This routes on M1
# This routes on route_layer
# Midpoints of the L routes goes vertical first then horizontal
mid1_offset = vector(top_pin_offset.x, out_offset.y)
# Midpoints of the L routes goes horizontal first then vertical
mid2_offset = vector(out_offset.x, bottom_pin_offset.y)
self.add_path("m1",
[top_pin_offset, mid1_offset, out_offset])
# Route in two segments to have the width rule
self.add_path("m1",
[bottom_pin_offset, mid2_offset + vector(0.5 * self.m1_width, 0)],
width=nmos_pin.height())
self.add_path("m1",
[mid2_offset, out_offset])
self.add_path(self.route_layer,
[top_pin_offset, mid1_offset, out_offset, bottom_pin_offset])
# This extends the output to the edge of the cell
self.add_layout_pin_rect_center(text="Z",
layer="m1",
offset=out_offset,
width=contact.m1_via.first_layer_width,
height=contact.m1_via.first_layer_height)
layer=self.route_layer,
offset=out_offset)
def analytical_power(self, corner, load):
"""Returns dynamic and leakage power. Results in nW"""

View File

@ -74,48 +74,59 @@ class pnand3(pgate.pgate):
def add_ptx(self):
""" Create the PMOS and NMOS transistors. """
self.nmos_nsnd = factory.create(module_type="ptx",
self.nmos_center = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_source_contact="active",
add_drain_contact="active")
self.add_mod(self.nmos_center)
self.nmos_right = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_source_contact="active",
add_drain_contact=self.route_layer)
self.add_mod(self.nmos_right)
self.nmos_left = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_source_contact=False,
add_drain_contact=False,
connect_poly=True,
connect_active=True)
self.add_mod(self.nmos_nsnd)
self.nmos_ns = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_source_contact=False,
connect_poly=True,
connect_active=True)
self.add_mod(self.nmos_ns)
self.nmos_nd = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_drain_contact=False,
connect_poly=True,
connect_active=True)
self.add_mod(self.nmos_nd)
add_source_contact="m1",
add_drain_contact="active")
self.add_mod(self.nmos_left)
self.pmos = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
connect_poly=True,
connect_active=True)
self.add_mod(self.pmos)
self.pmos_left = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
add_source_contact="m1",
add_drain_contact=self.route_layer)
self.add_mod(self.pmos_left)
self.pmos_center = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
add_source_contact=self.route_layer,
add_drain_contact="m1")
self.add_mod(self.pmos_center)
self.pmos_right = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
add_source_contact="m1",
add_drain_contact=self.route_layer)
self.add_mod(self.pmos_right)
def setup_layout_constants(self):
""" Pre-compute some handy layout parameters. """
# Compute the overlap of the source and drain pins
overlap_xoffset = self.pmos.get_pin("D").ll().x - self.pmos.get_pin("S").ll().x
self.ptx_offset = vector(overlap_xoffset, 0)
self.ptx_offset = self.pmos_left.get_pin("D").center() - self.pmos_left.get_pin("S").center()
# This is the extra space needed to ensure DRC rules
# to the active contacts
@ -143,27 +154,27 @@ class pnand3(pgate.pgate):
"""
self.pmos1_inst = self.add_inst(name="pnand3_pmos1",
mod=self.pmos)
mod=self.pmos_left)
self.connect_inst(["vdd", "A", "Z", "vdd"])
self.pmos2_inst = self.add_inst(name="pnand3_pmos2",
mod=self.pmos)
mod=self.pmos_center)
self.connect_inst(["Z", "B", "vdd", "vdd"])
self.pmos3_inst = self.add_inst(name="pnand3_pmos3",
mod=self.pmos)
mod=self.pmos_right)
self.connect_inst(["Z", "C", "vdd", "vdd"])
self.nmos1_inst = self.add_inst(name="pnand3_nmos1",
mod=self.nmos_nd)
mod=self.nmos_left)
self.connect_inst(["Z", "C", "net1", "gnd"])
self.nmos2_inst = self.add_inst(name="pnand3_nmos2",
mod=self.nmos_nsnd)
mod=self.nmos_center)
self.connect_inst(["net1", "B", "net2", "gnd"])
self.nmos3_inst = self.add_inst(name="pnand3_nmos3",
mod=self.nmos_ns)
mod=self.nmos_right)
self.connect_inst(["net2", "A", "gnd", "gnd"])
def place_ptx(self):
@ -172,8 +183,8 @@ class pnand3(pgate.pgate):
and lowest position to provide maximum routing in channel
"""
pmos1_pos = vector(self.pmos.active_offset.x,
self.height - self.pmos.active_height - self.top_bottom_space)
pmos1_pos = vector(self.pmos_left.active_offset.x,
self.height - self.pmos_left.active_height - self.top_bottom_space)
self.pmos1_inst.place(pmos1_pos)
pmos2_pos = pmos1_pos + self.ptx_offset
@ -182,7 +193,7 @@ class pnand3(pgate.pgate):
self.pmos3_pos = pmos2_pos + self.ptx_offset
self.pmos3_inst.place(self.pmos3_pos)
nmos1_pos = vector(self.pmos.active_offset.x,
nmos1_pos = vector(self.pmos_left.active_offset.x,
self.top_bottom_space)
self.nmos1_inst.place(nmos1_pos)
@ -195,9 +206,9 @@ class pnand3(pgate.pgate):
def add_well_contacts(self):
""" Add n/p well taps to the layout and connect to supplies """
self.add_nwell_contact(self.pmos,
self.add_nwell_contact(self.pmos_right,
self.pmos3_pos + vector(self.m1_pitch, 0))
self.add_pwell_contact(self.nmos_ns,
self.add_pwell_contact(self.nmos_right,
self.nmos3_pos + vector(self.m1_pitch, 0))
def connect_rails(self):
@ -250,14 +261,9 @@ class pnand3(pgate.pgate):
# NMOS3 drain
nmos3_pin = self.nmos3_inst.get_pin("D")
# midpoint for routing
mid_offset = vector(nmos3_pin.cx() + self.m1_pitch,
out_offset = vector(nmos3_pin.cx() + self.route_layer_pitch,
self.inputA_yoffset)
# Aligned with the well taps
out_offset = vector(self.nwell_contact.cx(),
self.inputA_yoffset)
# Go up to metal2 for ease on all output pins
# self.add_via_center(layers=self.m1_stack,
# offset=pmos1_pin.center(),
@ -282,26 +288,24 @@ class pnand3(pgate.pgate):
bottom_pin_offset = nmos3_pin.center()
# PMOS1 to output
self.add_path("m1", [top_left_pin_offset,
vector(top_left_pin_offset.x, out_offset.y),
out_offset])
self.add_path(self.route_layer, [top_left_pin_offset,
vector(top_left_pin_offset.x, out_offset.y),
out_offset])
# PMOS3 to output
self.add_path("m1", [top_right_pin_offset,
vector(top_right_pin_offset.x, mid_offset.y),
mid_offset])
self.add_path(self.route_layer, [top_right_pin_offset,
vector(top_right_pin_offset.x, out_offset.y),
out_offset])
# NMOS3 to output
mid2_offset = vector(mid_offset.x, bottom_pin_offset.y)
self.add_path("m1",
mid2_offset = vector(out_offset.x, bottom_pin_offset.y)
self.add_path(self.route_layer,
[bottom_pin_offset, mid2_offset],
width=nmos3_pin.height())
mid3_offset = vector(mid_offset.x, nmos3_pin.by())
self.add_path("m1", [mid3_offset, mid_offset])
mid3_offset = vector(out_offset.x, nmos3_pin.by())
self.add_path(self.route_layer, [mid3_offset, out_offset])
self.add_layout_pin_rect_center(text="Z",
layer="m1",
offset=out_offset,
width=contact.m1_via.first_layer_width,
height=contact.m1_via.first_layer_height)
layer=self.route_layer,
offset=out_offset)
def analytical_power(self, corner, load):
"""Returns dynamic and leakage power. Results in nW"""

View File

@ -71,31 +71,37 @@ class pnor2(pgate.pgate):
def add_ptx(self):
""" Create the PMOS and NMOS transistors. """
self.nmos = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
connect_poly=True,
connect_active=True)
self.add_mod(self.nmos)
self.nmos_left = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_source_contact="m1",
add_drain_contact=self.route_layer)
self.add_mod(self.nmos_left)
self.pmos_nd = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
add_drain_contact=False,
connect_poly=True,
connect_active=True)
self.add_mod(self.pmos_nd)
self.nmos_right = factory.create(module_type="ptx",
width=self.nmos_width,
mults=self.tx_mults,
tx_type="nmos",
add_source_contact=self.route_layer,
add_drain_contact="m1")
self.add_mod(self.nmos_right)
self.pmos_left = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
add_source_contact="m1",
add_drain_contact="active")
self.add_mod(self.pmos_left)
self.pmos_ns = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
add_source_contact=False,
connect_poly=True,
connect_active=True)
self.add_mod(self.pmos_ns)
self.pmos_right = factory.create(module_type="ptx",
width=self.pmos_width,
mults=self.tx_mults,
tx_type="pmos",
add_source_contact="active",
add_drain_contact=self.route_layer)
self.add_mod(self.pmos_right)
def setup_layout_constants(self):
""" Pre-compute some handy layout parameters. """
@ -108,12 +114,12 @@ class pnor2(pgate.pgate):
# Compute the other pmos2 location, but determining
# offset to overlap the source and drain pins
self.overlap_offset = self.pmos_ns.get_pin("D").ll() - self.pmos_nd.get_pin("S").ll()
self.overlap_offset = self.pmos_right.get_pin("D").center() - self.pmos_left.get_pin("S").center()
# Two PMOS devices and a well contact. Separation between each.
# Enclosure space on the sides.
self.width = 2 * self.pmos_ns.active_width \
+ self.pmos_ns.active_contact.width \
self.width = 2 * self.pmos_right.active_width \
+ self.pmos_right.active_contact.width \
+ 2 * self.active_space \
+ 0.5 * self.nwell_enclose_active
self.well_width = self.width + 2 * self.nwell_enclose_active
@ -121,7 +127,7 @@ class pnor2(pgate.pgate):
# This is the extra space needed to ensure DRC rules
# to the active contacts
extra_contact_space = max(-self.nmos.get_pin("D").by(), 0)
extra_contact_space = max(-self.nmos_right.get_pin("D").by(), 0)
# This is a poly-to-poly of a flipped cell
self.top_bottom_space = max(0.5 * self.m1_width + self.m1_space + extra_contact_space,
self.poly_extend_active,
@ -146,19 +152,19 @@ class pnor2(pgate.pgate):
"""
self.pmos1_inst = self.add_inst(name="pnor2_pmos1",
mod=self.pmos_nd)
mod=self.pmos_left)
self.connect_inst(["vdd", "A", "net1", "vdd"])
self.pmos2_inst = self.add_inst(name="pnor2_pmos2",
mod=self.pmos_ns)
mod=self.pmos_right)
self.connect_inst(["net1", "B", "Z", "vdd"])
self.nmos1_inst = self.add_inst(name="pnor2_nmos1",
mod=self.nmos)
mod=self.nmos_left)
self.connect_inst(["Z", "A", "gnd", "gnd"])
self.nmos2_inst = self.add_inst(name="pnor2_nmos2",
mod=self.nmos)
mod=self.nmos_right)
self.connect_inst(["Z", "B", "gnd", "gnd"])
def place_ptx(self):
@ -167,29 +173,26 @@ class pnor2(pgate.pgate):
to provide maximum routing in channel
"""
pmos1_pos = vector(self.pmos_ns.active_offset.x,
self.height - self.pmos_ns.active_height \
pmos1_pos = vector(self.pmos_right.active_offset.x,
self.height - self.pmos_right.active_height \
- self.top_bottom_space)
self.pmos1_inst.place(pmos1_pos)
self.pmos2_pos = pmos1_pos + self.overlap_offset
self.pmos2_inst.place(self.pmos2_pos)
nmos1_pos = vector(self.pmos_ns.active_offset.x, self.top_bottom_space)
nmos1_pos = vector(self.pmos_right.active_offset.x, self.top_bottom_space)
self.nmos1_inst.place(nmos1_pos)
self.nmos2_pos = nmos1_pos + self.overlap_offset
self.nmos2_inst.place(self.nmos2_pos)
# Output position will be in between the PMOS and NMOS
self.output_pos = vector(0,
0.5 * (pmos1_pos.y + nmos1_pos.y + self.nmos.active_height))
def add_well_contacts(self):
""" Add n/p well taps to the layout and connect to supplies """
self.add_nwell_contact(self.pmos_ns, self.pmos2_pos)
self.add_pwell_contact(self.nmos, self.nmos2_pos)
self.add_nwell_contact(self.pmos_right, self.pmos2_pos)
self.add_pwell_contact(self.nmos_right, self.nmos2_pos)
def connect_rails(self):
""" Connect the nmos and pmos to its respective power rails """
@ -208,7 +211,7 @@ class pnor2(pgate.pgate):
self.nmos2_inst,
inputB_yoffset,
"B",
position="center")
position="right")
# This will help with the wells and the input/output placement
self.inputA_yoffset = inputB_yoffset + self.input_spacing
@ -217,38 +220,32 @@ class pnor2(pgate.pgate):
self.inputA_yoffset,
"A")
self.output_yoffset = self.inputA_yoffset + self.input_spacing
def route_output(self):
""" Route the Z output """
# PMOS2 drain
# PMOS2 (right) drain
pmos_pin = self.pmos2_inst.get_pin("D")
# NMOS1 drain
# NMOS1 (left) drain
nmos_pin = self.nmos1_inst.get_pin("D")
# NMOS2 drain (for output via placement)
# NMOS2 (right) drain (for output via placement)
nmos2_pin = self.nmos2_inst.get_pin("D")
# Go up to metal2 for ease on all output pins
self.add_via_center(layers=self.m1_stack,
offset=pmos_pin.center())
m1m2_contact = self.add_via_center(layers=self.m1_stack,
offset=nmos_pin.center())
mid1_offset = vector(pmos_pin.center().x, nmos2_pin.center().y)
mid2_offset = vector(pmos_pin.center().x, self.inputA_yoffset)
mid3_offset = mid2_offset + vector(self.m2_width, 0)
# self.add_via_center(layers=self.m1_stack,
# offset=pmos_pin.center())
# m1m2_contact = self.add_via_center(layers=self.m1_stack,
# offset=nmos_pin.center())
mid1_offset = vector(nmos_pin.center().x, self.output_yoffset)
mid2_offset = vector(pmos_pin.center().x, self.output_yoffset)
# PMOS1 to mid-drain to NMOS2 drain
self.add_path("m2",
[pmos_pin.center(), mid2_offset, mid3_offset])
self.add_path("m2",
[nmos_pin.rc(), mid1_offset, mid2_offset])
# This extends the output to the edge of the cell
self.add_via_center(layers=self.m1_stack,
offset=mid3_offset)
self.add_path(self.route_layer,
[nmos_pin.center(), mid1_offset, mid2_offset, pmos_pin.center()])
self.add_layout_pin_rect_center(text="Z",
layer="m1",
offset=mid3_offset,
width=contact.m1_via.first_layer_height,
height=contact.m1_via.first_layer_width)
layer=self.route_layer,
offset=mid2_offset)
def analytical_power(self, corner, load):
"""Returns dynamic and leakage power. Results in nW"""

View File

@ -114,7 +114,7 @@ class precharge(design.design):
self.add_power_pin("vdd",
self.well_contact_pos,
vertical=True)
directions=("V", "V"))
# Hack for li layers
if hasattr(self, "li_stack"):

View File

@ -23,32 +23,44 @@ class ptx(design.design):
the transistor width. Mults is the number of transistors of the
given width. Total width is therefore mults*width. Options allow
you to connect the fingered gates and active for parallel devices.
The add_*_contact option tells which layer to bring source/drain up to.
"""
def __init__(self,
name="",
width=drc("minwidth_tx"),
mults=1,
tx_type="nmos",
add_source_contact=True,
add_drain_contact=True,
add_source_contact=None,
add_drain_contact=None,
series_devices=False,
connect_active=False,
connect_drain_active=False,
connect_source_active=False,
connect_poly=False,
num_contacts=None):
if not add_source_contact and "li" in layer:
add_source_contact = "li"
elif not add_source_contact:
add_source_contact = "m1"
if not add_drain_contact and "li" in layer:
add_drain_contact = "li"
elif not add_drain_contact:
add_drain_contact = "m1"
# We need to keep unique names because outputting to GDSII
# will use the last record with a given name. I.e., you will
# over-write a design in GDS if one has and the other doesn't
# have poly connected, for example.
name = "{0}_m{1}_w{2:.3f}".format(tx_type, mults, width)
if not add_source_contact:
name += "_ns"
if not add_drain_contact:
name += "_nd"
name += "_s{}".format(add_source_contact)
name += "_d{}".format(add_drain_contact)
if series_devices:
name += "_sd"
if connect_active:
name += "_a"
if connect_drain_active:
name += "_da"
if connect_source_active:
name += "_sa"
if connect_poly:
name += "_p"
if num_contacts:
@ -61,13 +73,21 @@ class ptx(design.design):
self.tx_type = tx_type
self.mults = mults
self.tx_width = width
self.connect_active = connect_active
self.connect_drain_active = connect_drain_active
self.connect_source_active = connect_source_active
self.connect_poly = connect_poly
self.add_source_contact = add_source_contact
self.add_drain_contact = add_drain_contact
self.series_devices = series_devices
self.num_contacts = num_contacts
if "li" in layer:
self.route_layer = "li"
else:
self.route_layer = "m1"
self.route_layer_width = drc("minwidth_{}".format(self.route_layer))
self.route_layer_space = drc("{0}_to_{0}".format(self.route_layer))
# Since it has variable height, it is not a pgate.
self.create_netlist()
# We must always create ptx layout for pbitcell
@ -266,55 +286,44 @@ class ptx(design.design):
width=poly_width,
height=self.poly_width)
def connect_fingered_active(self, drain_positions, source_positions):
def connect_fingered_active(self, positions, pin_name, layer, top):
"""
Connect each contact up/down to a source or drain pin
"""
if top:
dir = 1
else:
dir = -1
if len(positions) <= 1:
return
debug.check(layer != "active", "Must specify a metal for source connections.")
layer_space = getattr(self, "{}_space".format(layer))
layer_width = getattr(self, "{}_width".format(layer))
# This is the distance that we must route up or down from the center
# of the contacts to avoid DRC violations to the other contacts
pin_offset = vector(0,
0.5 * self.active_contact.second_layer_height + self.m1_space + 0.5 * self.m1_width)
0.5 * self.active_contact.second_layer_height + layer_space + 0.5 * layer_width)
# This is the width of a m1 extend the ends of the pin
end_offset = vector(self.m1_width / 2.0, 0)
end_offset = vector(layer_width / 2.0, 0)
# drains always go to the MIDDLE of the cell,
# so top of NMOS, bottom of PMOS
# so reverse the directions for NMOS compared to PMOS.
if self.tx_type == "pmos":
drain_dir = -1
source_dir = 1
else:
drain_dir = 1
source_dir = -1
if len(source_positions) > 1:
source_offset = pin_offset.scale(source_dir, source_dir)
# remove the individual connections
self.remove_layout_pin("S")
# Add each vertical segment
for a in source_positions:
self.add_path(("m1"),
[a, a + pin_offset.scale(source_dir,
source_dir)])
# Add a single horizontal pin
self.add_layout_pin_segment_center(text="S",
layer="m1",
start=source_positions[0] + source_offset - end_offset,
end=source_positions[-1] + source_offset + end_offset)
offset = pin_offset.scale(dir, dir)
# remove the individual connections
self.remove_layout_pin(pin_name)
# Add each vertical segment
for a in positions:
self.add_path(self.add_source_contact,
[a, a + pin_offset.scale(dir, dir)])
# Add a single horizontal pin
self.add_layout_pin_segment_center(text=pin_name,
layer=layer,
start=positions[0] + offset - end_offset,
end=positions[-1] + offset + end_offset)
if len(drain_positions)>1:
drain_offset = pin_offset.scale(drain_dir,drain_dir)
self.remove_layout_pin("D") # remove the individual connections
# Add each vertical segment
for a in drain_positions:
self.add_path(("m1"), [a,a+drain_offset])
# Add a single horizontal pin
self.add_layout_pin_segment_center(text="D",
layer="m1",
start=drain_positions[0] + drain_offset - end_offset,
end=drain_positions[-1] + drain_offset + end_offset)
def add_poly(self):
"""
Add the poly gates(s) and (optionally) connect them.
@ -433,12 +442,12 @@ class ptx(design.design):
label = "S"
source_positions.append(pos)
if (label=="S" and self.add_source_contact) or (label=="D" and self.add_drain_contact):
if (label=="S" and self.add_source_contact):
contact = self.add_diff_contact(label, pos)
if label == "S":
self.source_contacts.append(contact)
else:
self.drain_contacts.append(contact)
self.source_contacts.append(contact)
elif (label=="D" and self.add_drain_contact):
contact = self.add_diff_contact(label, pos)
self.drain_contacts.append(contact)
else:
self.add_layout_pin_rect_center(text=label,
layer="active",
@ -454,19 +463,22 @@ class ptx(design.design):
label = "S"
source_positions.append(pos)
if (label=="S" and self.add_source_contact) or (label=="D" and self.add_drain_contact):
if (label=="S" and self.add_source_contact):
contact = self.add_diff_contact(label, pos)
if label == "S":
self.source_contacts.append(contact)
else:
self.drain_contacts.append(contact)
self.source_contacts.append(contact)
elif (label=="D" and self.add_drain_contact):
contact = self.add_diff_contact(label, pos)
self.drain_contacts.append(contact)
else:
self.add_layout_pin_rect_center(text=label,
layer="active",
offset=pos)
if self.connect_active:
self.connect_fingered_active(drain_positions, source_positions)
if self.connect_source_active:
self.connect_fingered_active(source_positions, "S", self.add_source_contact, top=(self.tx_type=="pmos"))
if self.connect_drain_active:
self.connect_fingered_active(drain_positions, "D", self.add_drain_contact, top=(self.tx_type=="nmos"))
def get_stage_effort(self, cout):
"""Returns an object representing the parameters for delay in tau units."""
@ -489,34 +501,36 @@ class ptx(design.design):
return self.mults * self.tx_width / drc("minwidth_tx")
def add_diff_contact(self, label, pos):
contact=self.add_via_center(layers=self.active_stack,
offset=pos,
size=(1, self.num_contacts),
directions=("V", "V"),
implant_type=self.implant_type,
well_type=self.well_type)
if hasattr(self, "li_stack"):
contact=self.add_via_center(layers=self.li_stack,
offset=pos,
directions=("V", "V"))
# contact_area = contact.mod.second_layer_width * contact.mod.second_layer_height
# min_area = drc("minarea_m1")
# width = contact.mod.second_layer_width
# if contact_area < min_area:
# height = min_area / width
# else:
# height = contact.mod.second_layer_height
width = contact.mod.second_layer_width
height = contact.mod.second_layer_height
if label == "S":
layer = self.add_source_contact
elif label == "D":
layer = self.add_drain_contact
else:
debug.error("Invalid source drain name.")
via=self.add_via_stack_center(offset=pos,
from_layer="active",
to_layer=layer,
size=(1, self.num_contacts),
directions=("V", "V"),
implant_type=self.implant_type,
well_type=self.well_type)
if layer == "active":
source_via = getattr(contact, "{}_contact".format(layer))
else:
source_via = getattr(contact, "{}_via".format(layer))
# Source drain vias are all vertical
pin_height = source_via.first_layer_width
pin_width = source_via.first_layer_height
self.add_layout_pin_rect_center(text=label,
layer="m1",
layer=layer,
offset=pos,
width=width,
height=height)
width=pin_width,
height=pin_height)
return(contact)
return(via)
def get_cin(self):
"""Returns the relative gate cin of the tx"""

View File

@ -5,21 +5,15 @@
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import gdsMill
import tech
import math
import debug
from globals import OPTS,print_time
from contact import contact
from pin_group import pin_group
from pin_layout import pin_layout
from vector3d import vector3d
from globals import print_time
from vector3d import vector3d
from router import router
from direction import direction
from datetime import datetime
import grid
import grid_utils
class supply_grid_router(router):
"""
A router class to read an obstruction map from a gds and
@ -44,14 +38,13 @@ class supply_grid_router(router):
self.supply_rail_tracks = {}
print_time("Init supply router", datetime.now(), start_time, 3)
def create_routing_grid(self):
"""
Create a sprase routing grid with A* expansion functions.
"""
size = self.ur - self.ll
debug.info(1,"Size: {0} x {1}".format(size.x,size.y))
debug.info(1, "Size: {0} x {1}".format(size.x, size.y))
import supply_grid
self.rg = supply_grid.supply_grid(self.ll, self.ur, self.track_width)
@ -60,12 +53,12 @@ class supply_grid_router(router):
"""
Add power supply rails and connect all pins to these rails.
"""
debug.info(1,"Running supply router on {0} and {1}...".format(vdd_name, gnd_name))
debug.info(1, "Running supply router on {0} and {1}...".format(vdd_name, gnd_name))
self.vdd_name = vdd_name
self.gnd_name = gnd_name
# Clear the pins if we have previously routed
if (hasattr(self,'rg')):
if (hasattr(self, 'rg')):
self.clear_pins()
else:
# Creat a routing grid over the entire area
@ -76,32 +69,32 @@ class supply_grid_router(router):
# Get the pin shapes
start_time = datetime.now()
self.find_pins_and_blockages([self.vdd_name, self.gnd_name])
print_time("Finding pins and blockages",datetime.now(), start_time, 3)
print_time("Finding pins and blockages", datetime.now(), start_time, 3)
# Add the supply rails in a mesh network and connect H/V with vias
start_time = datetime.now()
# Block everything
self.prepare_blockages(self.gnd_name)
# Determine the rail locations
self.route_supply_rails(self.gnd_name,0)
self.route_supply_rails(self.gnd_name, 0)
# Block everything
self.prepare_blockages(self.vdd_name)
# Determine the rail locations
self.route_supply_rails(self.vdd_name,1)
print_time("Routing supply rails",datetime.now(), start_time, 3)
self.route_supply_rails(self.vdd_name, 1)
print_time("Routing supply rails", datetime.now(), start_time, 3)
start_time = datetime.now()
self.route_simple_overlaps(vdd_name)
self.route_simple_overlaps(gnd_name)
print_time("Simple overlap routing",datetime.now(), start_time, 3)
print_time("Simple overlap routing", datetime.now(), start_time, 3)
# Route the supply pins to the supply rails
# Route vdd first since we want it to be shorter
start_time = datetime.now()
self.route_pins_to_rails(vdd_name)
self.route_pins_to_rails(gnd_name)
print_time("Maze routing supplies",datetime.now(), start_time, 3)
#self.write_debug_gds("final.gds",False)
print_time("Maze routing supplies", datetime.now(), start_time, 3)
# self.write_debug_gds("final.gds", False)
# Did we route everything??
if not self.check_all_routed(vdd_name):
@ -111,9 +104,8 @@ class supply_grid_router(router):
return True
def check_all_routed(self, pin_name):
"""
"""
Check that all pin groups are routed.
"""
for pg in self.pin_groups[pin_name]:
@ -125,7 +117,7 @@ class supply_grid_router(router):
This checks for simple cases where a pin component already overlaps a supply rail.
It will add an enclosure to ensure the overlap in wide DRC rule cases.
"""
debug.info(1,"Routing simple overlap pins for {0}".format(pin_name))
debug.info(1, "Routing simple overlap pins for {0}".format(pin_name))
# These are the wire tracks
wire_tracks = self.supply_rail_tracks[pin_name]
@ -142,10 +134,10 @@ class supply_grid_router(router):
continue
# Else, if we overlap some of the space track, we can patch it with an enclosure
#pg.create_simple_overlap_enclosure(pg.grids)
#pg.add_enclosure(self.cell)
# pg.create_simple_overlap_enclosure(pg.grids)
# pg.add_enclosure(self.cell)
debug.info(1,"Routed {} simple overlap pins".format(routed_count))
debug.info(1, "Routed {} simple overlap pins".format(routed_count))
def finalize_supply_rails(self, name):
"""
@ -158,7 +150,7 @@ class supply_grid_router(router):
connections = set()
via_areas = []
for i1,r1 in enumerate(all_rails):
for i1, r1 in enumerate(all_rails):
# Only consider r1 horizontal rails
e = next(iter(r1))
if e.z==1:
@ -166,9 +158,9 @@ class supply_grid_router(router):
# We need to move this rail to the other layer for the z indices to match
# during the intersection. This also makes a copy.
new_r1 = {vector3d(i.x,i.y,1) for i in r1}
new_r1 = {vector3d(i.x, i.y, 1) for i in r1}
for i2,r2 in enumerate(all_rails):
for i2, r2 in enumerate(all_rails):
# Never compare to yourself
if i1==i2:
continue
@ -184,16 +176,16 @@ class supply_grid_router(router):
# the overlap area for placement of a via
overlap = new_r1 & r2
if len(overlap) >= 1:
debug.info(3,"Via overlap {0} {1}".format(len(overlap),overlap))
connections.update([i1,i2])
debug.info(3, "Via overlap {0} {1}".format(len(overlap),overlap))
connections.update([i1, i2])
via_areas.append(overlap)
# Go through and add the vias at the center of the intersection
for area in via_areas:
ll = grid_utils.get_lower_left(area)
ur = grid_utils.get_upper_right(area)
center = (ll + ur).scale(0.5,0.5,0)
self.add_via(center,1)
center = (ll + ur).scale(0.5, 0.5, 0)
self.add_via(center, 1)
# Determien which indices were not connected to anything above
missing_indices = set([x for x in range(len(self.supply_rails[name]))])
@ -204,13 +196,12 @@ class supply_grid_router(router):
for rail_index in sorted(missing_indices, reverse=True):
ll = grid_utils.get_lower_left(all_rails[rail_index])
ur = grid_utils.get_upper_right(all_rails[rail_index])
debug.info(1,"Removing disconnected supply rail {0} .. {1}".format(ll,ur))
debug.info(1, "Removing disconnected supply rail {0} .. {1}".format(ll, ur))
self.supply_rails[name].pop(rail_index)
# Make the supply rails into a big giant set of grids for easy blockages.
# Must be done after we determine which ones are connected.
self.create_supply_track_set(name)
def add_supply_rails(self, name):
"""
@ -223,7 +214,7 @@ class supply_grid_router(router):
ur = grid_utils.get_upper_right(rail)
z = ll.z
pin = self.compute_pin_enclosure(ll, ur, z, name)
debug.info(3,"Adding supply rail {0} {1}->{2} {3}".format(name,ll,ur,pin))
debug.info(3, "Adding supply rail {0} {1}->{2} {3}".format(name, ll, ur, pin))
self.cell.add_layout_pin(text=name,
layer=pin.layer,
offset=pin.ll(),
@ -243,19 +234,18 @@ class supply_grid_router(router):
max_xoffset = self.rg.ur.x
min_yoffset = self.rg.ll.y
min_xoffset = self.rg.ll.x
# Horizontal supply rails
start_offset = min_yoffset + supply_number
for offset in range(start_offset, max_yoffset, 2):
# Seed the function at the location with the given width
wave = [vector3d(min_xoffset,offset,0)]
wave = [vector3d(min_xoffset, offset, 0)]
# While we can keep expanding east in this horizontal track
while wave and wave[0].x < max_xoffset:
added_rail = self.find_supply_rail(name, wave, direction.EAST)
if not added_rail:
# Just seed with the next one
wave = [x+vector3d(1,0,0) for x in wave]
wave = [x+vector3d(1, 0, 0) for x in wave]
else:
# Seed with the neighbor of the end of the last rail
wave = added_rail.neighbor(direction.EAST)
@ -264,15 +254,15 @@ class supply_grid_router(router):
start_offset = min_xoffset + supply_number
for offset in range(start_offset, max_xoffset, 2):
# Seed the function at the location with the given width
wave = [vector3d(offset,min_yoffset,1)]
wave = [vector3d(offset, min_yoffset, 1)]
# While we can keep expanding north in this vertical track
while wave and wave[0].y < max_yoffset:
added_rail = self.find_supply_rail(name, wave, direction.NORTH)
if not added_rail:
# Just seed with the next one
wave = [x+vector3d(0,1,0) for x in wave]
wave = [x + vector3d(0, 1, 0) for x in wave]
else:
# Seed with the neighbor of the end of the last rail
# Seed with the neighbor of the end of the last rail
wave = added_rail.neighbor(direction.NORTH)
def find_supply_rail(self, name, seed_wave, direct):
@ -294,7 +284,6 @@ class supply_grid_router(router):
# Return the rail whether we approved it or not,
# as it will be used to find the next start location
return wave_path
def probe_supply_rail(self, name, start_wave, direct):
"""
@ -328,23 +317,19 @@ class supply_grid_router(router):
data structure. Return whether it was added or not.
"""
# We must have at least 2 tracks to drop plus 2 tracks for a via
if len(wave_path)>=4*self.rail_track_width:
if len(wave_path) >= 4 * self.rail_track_width:
grid_set = wave_path.get_grids()
self.supply_rails[name].append(grid_set)
return True
return False
def route_supply_rails(self, name, supply_number):
"""
Route the horizontal and vertical supply rails across the entire design.
Must be done with lower left at 0,0
"""
debug.info(1,"Routing supply rail {0}.".format(name))
debug.info(1, "Routing supply rail {0}.".format(name))
# Compute the grid locations of the supply rails
self.compute_supply_rails(name, supply_number)
@ -355,7 +340,6 @@ class supply_grid_router(router):
# Add the rails themselves
self.add_supply_rails(name)
def create_supply_track_set(self, pin_name):
"""
Make a single set of all the tracks for the rail and wire itself.
@ -364,24 +348,22 @@ class supply_grid_router(router):
for rail in self.supply_rails[pin_name]:
rail_set.update(rail)
self.supply_rail_tracks[pin_name] = rail_set
def route_pins_to_rails(self, pin_name):
"""
This will route each of the remaining pin components to the supply rails.
This will route each of the remaining pin components to the supply rails.
After it is done, the cells are added to the pin blockage list.
"""
remaining_components = sum(not x.is_routed() for x in self.pin_groups[pin_name])
debug.info(1,"Maze routing {0} with {1} pin components to connect.".format(pin_name,
remaining_components))
debug.info(1, "Maze routing {0} with {1} pin components to connect.".format(pin_name,
remaining_components))
for index,pg in enumerate(self.pin_groups[pin_name]):
for index, pg in enumerate(self.pin_groups[pin_name]):
if pg.is_routed():
continue
debug.info(3,"Routing component {0} {1}".format(pin_name, index))
debug.info(3, "Routing component {0} {1}".format(pin_name, index))
# Clear everything in the routing grid.
self.rg.reinit()
@ -400,28 +382,26 @@ class supply_grid_router(router):
# Actually run the A* router
if not self.run_router(detour_scale=5):
self.write_debug_gds("debug_route.gds",False)
self.write_debug_gds("debug_route.gds", False)
#if index==3 and pin_name=="vdd":
# self.write_debug_gds("route.gds",False)
# if index==3 and pin_name=="vdd":
# self.write_debug_gds("route.gds",False)
def add_supply_rail_target(self, pin_name):
"""
Add the supply rails of given name as a routing target.
"""
debug.info(4,"Add supply rail target {}".format(pin_name))
debug.info(4, "Add supply rail target {}".format(pin_name))
# Add the wire itself as the target
self.rg.set_target(self.supply_rail_tracks[pin_name])
# But unblock all the rail tracks including the space
self.rg.set_blocked(self.supply_rail_tracks[pin_name],False)
self.rg.set_blocked(self.supply_rail_tracks[pin_name], False)
def set_supply_rail_blocked(self, value=True):
"""
Add the supply rails of given name as a routing target.
"""
debug.info(4,"Blocking supply rail")
debug.info(4, "Blocking supply rail")
for rail_name in self.supply_rail_tracks:
self.rg.set_blocked(self.supply_rail_tracks[rail_name])

View File

@ -27,7 +27,8 @@ class ptx_3finger_nmos_test(openram_test):
width=tech.drc["minwidth_tx"],
mults=3,
tx_type="nmos",
connect_active=True,
connect_source_active=True,
connect_drain_active=True,
connect_poly=True)
self.local_drc_check(fet)

View File

@ -27,7 +27,8 @@ class ptx_3finger_pmos_test(openram_test):
width=tech.drc["minwidth_tx"],
mults=3,
tx_type="pmos",
connect_active=True,
connect_source_active=True,
connect_drain_active=True,
connect_poly=True)
self.local_drc_check(fet)

View File

@ -23,11 +23,12 @@ class ptx_4finger_nmos_test(openram_test):
import tech
debug.info(2, "Checking three fingers NMOS")
fet = factory.create(module_type="ptx",
fet = factory.create(module_type="ptx",
width= tech.drc["minwidth_tx"],
mults=4,
tx_type="nmos",
connect_active=True,
connect_source_active=True,
connect_drain_active=True,
connect_poly=True)
self.local_drc_check(fet)

View File

@ -23,11 +23,12 @@ class ptx_test(openram_test):
import tech
debug.info(2, "Checking three fingers PMOS")
fet = factory.create(module_type="ptx",
fet = factory.create(module_type="ptx",
width=tech.drc["minwidth_tx"],
mults=4,
tx_type="pmos",
connect_active=True,
connect_source_active=True,
connect_drain_active=True,
connect_poly=True)
self.local_drc_check(fet)

View File

@ -0,0 +1,60 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import unittest
from testutils import *
import sys,os
sys.path.append(os.getenv("OPENRAM_HOME"))
import globals
from globals import OPTS
from sram_factory import factory
import debug
class ptx_no_contacts_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
import tech
debug.info(2, "Checking single finger no source/drain")
fet = factory.create(module_type="ptx",
width=tech.drc["minwidth_tx"],
mults=1,
add_source_contact=False,
add_drain_contact=False,
tx_type="nmos")
self.local_drc_check(fet)
debug.info(2, "Checking multifinger no source/drain")
fet = factory.create(module_type="ptx",
width=tech.drc["minwidth_tx"],
mults=4,
add_source_contact=False,
add_drain_contact=False,
tx_type="nmos")
self.local_drc_check(fet)
debug.info(2, "Checking series ptx")
fet = factory.create(module_type="ptx",
width=tech.drc["minwidth_tx"],
mults=4,
series_devices=True,
tx_type="nmos")
self.local_drc_check(fet)
globals.end_openram()
# run the test from the command line
if __name__ == "__main__":
(OPTS, args) = globals.parse_args()
del sys.argv[1:]
header(__file__, OPTS.tech_name)
unittest.main(testRunner=debugTestRunner())

View File

@ -0,0 +1,36 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import unittest
from testutils import *
import sys,os
sys.path.append(os.getenv("OPENRAM_HOME"))
import globals
from globals import OPTS
from sram_factory import factory
import debug
class pinv_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
debug.info(2, "Checking 100x inverter")
tx = factory.create(module_type="pinv", size=100)
self.local_check(tx)
globals.end_openram()
# run the test from the command line
if __name__ == "__main__":
(OPTS, args) = globals.parse_args()
del sys.argv[1:]
header(__file__, OPTS.tech_name)
unittest.main(testRunner=debugTestRunner())

View File

@ -0,0 +1,36 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import unittest
from testutils import *
import sys, os
sys.path.append(os.getenv("OPENRAM_HOME"))
import globals
from globals import OPTS
from sram_factory import factory
import debug
class control_logic_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
debug.info(1, "Testing sample for control_logic_r")
a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=32, port_type="r")
self.local_check(a)
globals.end_openram()
# run the test from the command line
if __name__ == "__main__":
(OPTS, args) = globals.parse_args()
del sys.argv[1:]
header(__file__, OPTS.tech_name)
unittest.main(testRunner=debugTestRunner())

View File

@ -0,0 +1,35 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import unittest
from testutils import *
import sys,os
sys.path.append(os.getenv("OPENRAM_HOME"))
import globals
from globals import OPTS
from sram_factory import factory
import debug
class control_logic_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
debug.info(1, "Testing sample for control_logic_w")
a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=32, port_type="w")
self.local_check(a)
globals.end_openram()
# run the test from the command line
if __name__ == "__main__":
(OPTS, args) = globals.parse_args()
del sys.argv[1:]
header(__file__, OPTS.tech_name)
unittest.main(testRunner=debugTestRunner())