Merge branch 'dev' into no_rbl

This commit is contained in:
Sam Crow
2023-05-03 15:24:03 -07:00
149 changed files with 4151 additions and 779 deletions
+1
View File
@@ -72,6 +72,7 @@ from .replica_pbitcell import *
from .row_cap_array import *
from .row_cap_bitcell_1port import *
from .row_cap_bitcell_2port import *
from .rom_bank import *
from .sense_amp_array import *
from .sense_amp import *
from .tri_gate_array import *
@@ -20,3 +20,10 @@ class col_cap_bitcell_1port(bitcell_base):
debug.info(2, "Create col_cap bitcell 1 port object")
self.no_instances = True
def build_graph(self, graph, inst_name, port_nets):
"""
Adds edges based on inputs/outputs.
Overrides base class function.
"""
pass
@@ -20,3 +20,10 @@ class col_cap_bitcell_2port(bitcell_base):
debug.info(2, "Create col_cap bitcell 2 port object")
self.no_instances = True
def build_graph(self, graph, inst_name, port_nets):
"""
Adds edges based on inputs/outputs.
Overrides base class function.
"""
pass
+6 -4
View File
@@ -15,7 +15,7 @@ class pbuf_dec(pgate):
"""
This is a simple buffer used for driving wordlines.
"""
def __init__(self, name, size=4, height=None):
def __init__(self, name, size=4, height=None, add_wells=True):
debug.info(1, "creating {0} with size of {1}".format(name, size))
self.add_comment("size: {}".format(size))
@@ -25,7 +25,7 @@ class pbuf_dec(pgate):
self.height = height
# Creates the netlist and layout
pgate.__init__(self, name, height)
pgate.__init__(self, name, height, add_wells)
def create_netlist(self):
self.add_pins()
@@ -51,11 +51,13 @@ class pbuf_dec(pgate):
input_size = max(1, int(self.size / self.stage_effort))
self.inv1 = factory.create(module_type="pinv_dec",
size=input_size,
height=self.height)
height=self.height,
add_wells=self.add_wells)
self.inv2 = factory.create(module_type="pinv_dec",
size=self.size,
height=self.height)
height=self.height,
add_wells=self.add_wells)
def create_insts(self):
self.inv1_inst = self.add_inst(name="buf_inv1",
+13 -5
View File
@@ -20,7 +20,7 @@ class pinv_dec(pinv):
Other stuff is the same (netlist, sizes, etc.)
"""
def __init__(self, name, size=1, beta=parameter["beta"], height=None, add_wells=True):
def __init__(self, name, size=1, beta=parameter["beta"], height=None, add_wells=True, flip_io=False):
debug.info(2,
"creating pinv_dec structure {0} with size of {1}".format(name,
@@ -37,7 +37,7 @@ class pinv_dec(pinv):
self.supply_layer = "m1"
else:
self.supply_layer = "m2"
self.flip_io=flip_io
super().__init__(name, size, beta, self.cell_height, add_wells)
def determine_tx_mults(self):
@@ -76,9 +76,14 @@ class pinv_dec(pinv):
pmos_gate_pos = pmos_gate_pin.lc()
self.add_path("poly", [nmos_gate_pos, pmos_gate_pos])
# Center is completely symmetric.
contact_width = self.poly_contact.width
contact_offset = nmos_gate_pin.lc() \
if self.flip_io:
contact_offset = pmos_gate_pin.rc() \
+ vector(self.poly_extend_active + 0.6 * contact_width, 0)
else:
contact_offset = nmos_gate_pin.lc() \
- vector(self.poly_extend_active + 0.5 * contact_width, 0)
via = self.add_via_stack_center(from_layer="poly",
to_layer=self.route_layer,
@@ -107,8 +112,11 @@ class pinv_dec(pinv):
height=self.height - ll.y + 0.5 * self.pwell_contact.height + self.well_enclose_active)
if "nwell" in layer:
ll = (self.pmos_inst.ll() - vector(2 * [self.well_enclose_active])).scale(1, 0)
ur = self.pmos_inst.ur() + vector(2 * [self.well_enclose_active])
poly_offset = 0
if self.flip_io:
poly_offset = 1.2 * self.poly_contact.width
ll = (self.pmos_inst.ll() - vector(2 * [self.well_enclose_active])).scale(1, 0) - vector(0, poly_offset)
ur = self.pmos_inst.ur() + vector(2 * [self.well_enclose_active + poly_offset])
self.add_rect(layer="nwell",
offset=ll,
width=ur.x - ll.x,
+5 -4
View File
@@ -132,15 +132,16 @@ class pinvbuf(pgate):
to_layer=a3_pin.layer,
offset=a3_pin.center())
# inv1 Z to inv4 A (up and over)
z1_pin = self.inv1_inst.get_pin("Z")
a4_pin = self.inv4_inst.get_pin("A")
# inv1 Z to inv4 A (up and over)
mid_point = vector(z1_pin.cx(), a4_pin.cy())
self.add_wire(route_stack,
[z1_pin.center(), mid_point, a4_pin.center()])
[z1_pin.center(), mid_point, a4_pin.center()])
self.add_via_stack_center(from_layer=z1_pin.layer,
to_layer=route_stack[2],
offset=z1_pin.center())
to_layer=route_stack[2],
offset=z1_pin.center())
def add_layout_pins(self):
+4 -3
View File
@@ -39,7 +39,8 @@ class ptx(design):
connect_drain_active=False,
connect_source_active=False,
connect_poly=False,
num_contacts=None):
num_contacts=None,
):
if "li" in layer:
self.route_layer = "li"
@@ -47,11 +48,11 @@ class ptx(design):
self.route_layer = "m1"
# Default contacts are the lowest layer
if not add_source_contact:
if add_source_contact == None:
add_source_contact = self.route_layer
# Default contacts are the lowest layer
if not add_drain_contact:
if add_drain_contact == None:
add_drain_contact = self.route_layer
# We need to keep unique names because outputting to GDSII
@@ -0,0 +1,121 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 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.
#
from openram.base import design
from openram.sram_factory import factory
from openram.base import vector
from openram.tech import layer, drc
class rom_address_control_array(design):
"""
Takes the input address lines and creates the address and address bar lines for the decoder.
Adds control logic for the precharge cycle so that all address lines are high before the read cycle
"""
def __init__(self, cols, inv_height=None, inv_size=1, name="", route_layer="m1"):
self.size=inv_size
self.cols = cols
self.route_layer = route_layer
dff = factory.create(module_type="dff")
if name=="":
name = "rom_inv_array_{0}".format(cols)
if inv_height == None:
self.inv_height = dff.height * 0.5
else:
self.inv_height = inv_height
if "li" in layer:
self.inv_layer = "li"
else:
self.inv_layer = "m1"
super().__init__(name)
self.create_netlist()
self.create_layout()
def create_netlist(self):
self.create_modules()
self.add_pins()
self.create_instances()
def create_layout(self):
self.width = self.cols * self.addr_control.width
self.height = self.addr_control.height
self.setup_layout_constants()
self.place_instances()
self.route_clk()
self.route_sources()
self.copy_pins()
self.add_boundary()
def create_modules(self):
self.addr_control = factory.create(module_type="rom_address_control_buf", size=self.inv_height)
def add_pins(self):
for col in range(self.cols):
self.add_pin("A{0}_in".format(col), "INPUT")
for col in range(self.cols):
self.add_pin("A{0}_out".format(col), "OUTPUT")
for col in range(self.cols):
self.add_pin("Abar{0}_out".format(col), "OUTPUT")
self.add_pin("clk", "INPUT")
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
def create_instances(self):
self.buf_insts = []
for col in range(self.cols):
name = "Xaddr_buf_{0}".format(col)
addr_buf = self.add_inst(name=name, mod=self.addr_control)
A_in = "A{0}_in".format(col)
Aout = "A{0}_out".format(col)
Abar_out = "Abar{0}_out".format(col)
self.connect_inst([A_in, Aout, Abar_out, "clk", "vdd", "gnd"])
self.buf_insts.append(addr_buf)
def setup_layout_constants(self):
self.route_width = drc["minwidth_{}".format(self.route_layer)]
def place_instances(self):
for col in range(self.cols):
base = vector((col+1)*(self.addr_control.width), 0)
self.buf_insts[col].place(offset=base, mirror="MY")
def copy_pins(self):
for i in range(self.cols):
self.copy_layout_pin(self.buf_insts[i], "A_out", "A{0}_out".format(i))
self.copy_layout_pin(self.buf_insts[i], "Abar_out", "Abar{0}_out".format(i))
self.copy_layout_pin(self.buf_insts[i], "A_in", "A{0}_in".format(i))
def route_clk(self):
self.route_horizontal_pins("clk", insts=self.buf_insts, layer=self.route_layer)
def route_sources(self):
self.route_horizontal_pins("vdd", insts=self.buf_insts, layer=self.route_layer)
self.route_horizontal_pins("gnd", insts=self.buf_insts, layer=self.route_layer)
tmp_pins = []
for pin in self.get_pins("vdd"):
edge = vector(pin.lx() + 0.5 * self.route_width, pin.cy())
tmp_pins.append(self.add_layout_pin_rect_center("vdd_edge", layer=self.route_layer, offset=edge))
self.copy_layout_pin_shapes("vdd")
self.remove_layout_pin("vdd")
for pin in tmp_pins:
self.copy_layout_pin(self, "vdd_edge", "vdd")
self.remove_layout_pin("vdd_edge")
+185
View File
@@ -0,0 +1,185 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 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.
#
from openram.base import design
from openram.sram_factory import factory
from openram.base import vector
from openram.tech import layer, drc
class rom_address_control_buf(design):
"""
Takes the input address lines and creates the address and address bar lines for the decoder.
Adds control logic for the precharge cycle so that all address lines are high before the read cycle
"""
def __init__(self, size, name="", route_layer="m1", add_wells=False):
self.route_layer = route_layer
self.add_wells = add_wells
self.size = size
if "li" in layer:
self.inv_layer = "li"
else:
self.inv_layer = "m1"
super().__init__(name)
self.create_netlist()
self.create_layout()
def create_netlist(self):
self.create_modules()
self.add_pins()
self.create_instances()
def create_layout(self):
self.width = self.cell.height * 2
self.height = self.inv.width + 2 * self.nand.width
self.setup_layout_constants()
self.place_instances()
self.route_gates()
self.route_sources()
self.add_boundary()
def create_modules(self):
self.inv = factory.create(module_type="pinv_dec", module_name="inv_array_mod", add_wells=False, size=self.size)
self.nand = factory.create(module_type="nand2_dec", height=self.inv.height)
# For layout constants
self.cell = factory.create(module_type="rom_base_cell")
def add_pins(self):
self.add_pin("A_in", "INPUT")
self.add_pin("A_out", "INOUT")
self.add_pin("Abar_out", "OUTPUT")
self.add_pin("clk", "INPUT")
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
def create_instances(self):
name = "XinvAbar"
self.inv_inst = self.add_inst(name=name, mod=self.inv)
inst_A = "A_in"
inst_Z = "Abar_internal"
self.connect_inst([inst_A, inst_Z, "vdd", "gnd"])
name = "Xnand_addr"
self.addr_nand = self.add_inst(name=name, mod=self.nand)
inst_A = "clk"
inst_B = "Abar_internal"
inst_Z = "A_out"
self.connect_inst([inst_A, inst_B, inst_Z, "vdd", "gnd"])
name = "Xnand_addr_bar"
self.addr_bar_nand = self.add_inst(name=name, mod=self.nand)
inst_A = "clk"
inst_B = "A_out"
inst_Z = "Abar_out"
self.connect_inst([inst_A, inst_B, inst_Z, "vdd", "gnd"])
def setup_layout_constants(self):
self.route_width = drc["minwidth_{}".format(self.route_layer)]
self.interconnect_width = drc["minwidth_{}".format(self.inv_layer)]
def place_instances(self):
self.inv_inst.place(offset=vector(self.inv_inst.height,0), rotate=90)
self.addr_nand.place(offset=vector(self.addr_nand.height , self.inv_inst.width + self.route_width ), rotate=90)
self.addr_bar_nand.place(offset=vector( self.addr_bar_nand.height, self.addr_nand.width + self.inv_inst.width + self.route_width), rotate=90)
def route_gates(self):
clk1_pin = self.addr_nand.get_pin("A")
clk2_pin = self.addr_bar_nand.get_pin("A")
Abar_out = self.addr_bar_nand.get_pin("Z")
A_out = self.addr_nand.get_pin("Z")
Abar_in = self.addr_nand.get_pin("B")
Abar_int_out = self.inv_inst.get_pin("Z")
Aint_in = self.addr_bar_nand.get_pin("B")
A_in = self.inv_inst.get_pin("A")
# Find the center of the pmos poly/gate
poly_right = clk1_pin.cx() + self.poly_enclose_contact + 0.5 * self.contact_width
ppoly_center = poly_right - 0.7 * self.poly_width
contact_offset = vector(ppoly_center, clk2_pin.cy())
# Route the two shared clk inputs together by connecting poly
self.add_segment_center("poly", contact_offset, vector(ppoly_center, A_out.cy()))
clk_offset = vector(clk2_pin.cx(), self.addr_nand.uy())
self.add_layout_pin_rect_center("clk", offset=clk_offset, layer=self.route_layer)
self.add_via_stack_center(from_layer=self.inv_layer, to_layer=self.route_layer, offset=self.addr_bar_nand.get_pin("A").center())
self.add_segment_center(self.route_layer, clk_offset, vector(clk_offset.x, clk2_pin.cy()))
# Route first NAND output to second NAND input
start = A_out.center()
end = Aint_in.center()
self.add_path("m2", [start, end])
self.add_via_stack_center(Aint_in.center(), self.inv_layer, "m2")
self.add_via_stack_center(A_out.center(), self.inv_layer, "m2")
# Route first NAND to output pin
self.add_segment_center("m2", end, vector(end.x, self.addr_bar_nand.uy()))
self.add_layout_pin_rect_center("A_out", offset=vector(end.x, self.addr_bar_nand.uy() - 0.5 * self.m2_width), layer="m2")
# Route second NAND to output pin
self.add_via_stack_center(Abar_out.center(), self.inv_layer, "m2")
self.add_segment_center("m2", Abar_out.center(), vector(Abar_out.cx(), self.addr_bar_nand.uy()))
self.add_layout_pin_rect_center("Abar_out", offset=vector(Abar_out.cx(), self.addr_bar_nand.uy() - 0.5 * self.m2_width), layer="m2")
# Route inverter output to NAND
end = vector(Abar_int_out.cx(), Abar_in.cy() + 0.5 * self.interconnect_width)
self.add_segment_center(self.inv_layer, Abar_int_out.center(), end)
self.copy_layout_pin(self.inv_inst, "A", "A_in")
def route_sources(self):
self.copy_layout_pin(self.addr_nand, "vdd")
self.copy_layout_pin(self.addr_bar_nand, "vdd")
self.copy_layout_pin(self.inv_inst, "vdd")
self.copy_layout_pin(self.addr_bar_nand, "gnd")
self.copy_layout_pin(self.addr_nand, "gnd")
self.copy_layout_pin(self.inv_inst, "gnd")
""" Add n/p well taps to the layout and connect to supplies """
source_pin = self.inv_inst.get_pin("vdd")
gnd_pin = self.inv_inst.get_pin("gnd")
left_edge = self.inv_inst.get_pin("Z").cx() - 2 * self.contact_width - 2 * self.active_contact_to_gate - 4 * self.active_enclose_contact - self.poly_width - self.active_space
contact_pos = vector(left_edge, source_pin.cy())
self.add_via_center(layers=self.active_stack,
offset=contact_pos,
implant_type="n",
well_type="n")
self.add_via_stack_center(offset=contact_pos,
from_layer=self.active_stack[2],
to_layer=self.route_layer)
contact_pos = vector(left_edge, gnd_pin.cy())
self.add_via_center(layers=self.active_stack,
offset=contact_pos,
implant_type="p",
well_type="p")
self.add_via_stack_center(offset=contact_pos,
from_layer=self.active_stack[2],
to_layer=self.route_layer)
+512
View File
@@ -0,0 +1,512 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 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 datetime
from math import ceil, log
from openram.base import vector
from openram.base import design
from openram import OPTS, print_time
from openram.sram_factory import factory
from openram.tech import drc, layer, parameter
from openram.router import router_tech
class rom_bank(design):
"""
Rom data bank with row and column decoder + control logic
word size is in bytes
"""
def __init__(self, name, rom_config):
super().__init__(name=name)
self.rom_config = rom_config
rom_config.set_local_config(self)
self.word_size = self.word_bits
self.num_outputs = self.rows
self.num_inputs = ceil(log(self.rows, 2))
self.col_bits = ceil(log(self.words_per_row, 2))
self.row_bits = self.num_inputs
self.tap_spacing = self.strap_spacing
try:
from openram.tech import power_grid
self.supply_stack = power_grid
except ImportError:
# if no power_grid is specified by tech we use sensible defaults
# Route a M3/M4 grid
self.supply_stack = self.m3_stack
self.interconnect_layer = "m1"
self.bitline_layer = "m1"
self.wordline_layer = "m2"
if "li" in layer:
self.route_stack = self.m1_stack
else:
self.route_stack = self.m2_stack
self.route_layer = self.route_stack[0]
if OPTS.is_unit_test:
self.create_netlist()
self.create_layout()
def create_netlist(self):
start_time = datetime.datetime.now()
self.add_modules()
self.add_pins()
self.create_instances()
if not OPTS.is_unit_test:
print_time("Submodules", datetime.datetime.now(), start_time)
def create_layout(self):
start_time = datetime.datetime.now()
self.setup_layout_constants()
self.place_instances()
if not OPTS.is_unit_test:
print_time("Placement", datetime.datetime.now(), start_time)
self.add_boundary()
start_time = datetime.datetime.now()
self.route_layout()
if not OPTS.is_unit_test:
print_time("Routing", datetime.datetime.now(), start_time)
start_time = datetime.datetime.now()
if not OPTS.is_unit_test:
# We only enable final verification if we have routed the design
# Only run this if not a unit test, because unit test will also verify it.
self.DRC_LVS(final_verification=OPTS.route_supplies, force_check=OPTS.check_lvsdrc)
print_time("Verification", datetime.datetime.now(), start_time)
def add_boundary(self):
ll = self.find_lowest_coords()
m1_offset = self.m1_width
self.translate_all(vector(0, ll.y))
ur = self.find_highest_coords()
ur = vector(ur.x, ur.y)
super().add_boundary(vector(0, 0), ur)
self.width = ur.x
self.height = ur.y
def route_layout(self):
self.route_decode_outputs()
self.route_precharge()
self.route_clock()
self.route_array_outputs()
self.place_top_level_pins()
self.route_output_buffers()
rt = router_tech(self.supply_stack, 1)
init_bbox = self.get_bbox(side="ring",
margin=rt.track_width)
self.route_supplies(init_bbox)
# We need the initial bbox for the supply rings later
# because the perimeter pins will change the bbox
# Route the pins to the perimeter
if OPTS.perimeter_pins:
# We now route the escape routes far enough out so that they will
# reach past the power ring or stripes on the sides
bbox = self.get_bbox(side="ring",
margin=11*rt.track_width)
self.route_escape_pins(bbox)
def setup_layout_constants(self):
self.route_layer_width = drc["minwidth_{}".format(self.route_stack[0])]
self.route_layer_pitch = drc["{0}_to_{0}".format(self.route_stack[0])]
self.interconnect_layer_width = drc["minwidth_{}".format(self.interconnect_layer)]
self.interconnect_layer_pitch = drc["{0}_to_{0}".format(self.interconnect_layer)]
def add_pins(self):
self.add_pin("clk", "INPUT")
self.add_pin("cs", "INPUT")
for i in range(self.row_bits + self.col_bits):
self.add_pin("addr[{}]".format(i), "INPUT")
out_pins = []
for j in range(self.word_size):
out_pins.append("dout[{}]".format(j))
self.add_pin_list(out_pins, "OUTPUT")
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
def add_modules(self):
# TODO: provide technology-specific calculation of these parameters
# in sky130 the address control buffer is composed of 2 size 2 NAND gates,
# with a beta of 3, each of these gates has gate capacitance of 2 min sized inverters, therefor a load of 4
addr_control_buffer_effort = parameter['beta'] + 1
# a single min sized nmos makes up 1/4 of the input capacitance of a min sized inverter
bitcell_effort = 0.25
# Takes into account inverter sizing
wordline_effort = bitcell_effort * 0.5
# a single min sized pmos plus a single min sized nmos have approximately half the gate capacitance of a min inverter
# an additional 0.2 accounts for the long wire capacitance and add delay to gaurentee the read timing
precharge_cell_effort = 0.5 + 0.2
self.array = factory.create(module_type="rom_base_array",
cols=self.cols,
rows=self.rows,
strap_spacing=self.strap_spacing,
bitmap=self.data,
bitline_layer=self.bitline_layer,
wordline_layer=self.wordline_layer,
pitch_match=True,
tap_spacing=self.tap_spacing)
self.decode_array = factory.create(module_name="rom_row_decode",
module_type="rom_decoder",
num_outputs=self.rows,
strap_spacing=self.strap_spacing,
route_layer=self.route_layer,
fanout=(self.cols)*wordline_effort )
self.column_mux = factory.create(module_type="rom_column_mux_array",
columns=self.cols,
word_size=self.word_size,
tap_spacing=self.strap_spacing,
bitline_layer=self.interconnect_layer,
input_layer=self.bitline_layer)
self.column_decode = factory.create(module_name="rom_column_decode",
module_type="rom_decoder",
num_outputs=self.words_per_row,
strap_spacing=self.strap_spacing,
route_layer=self.route_layer,
fanout=2,
invert_outputs=True )
self.control_logic = factory.create(module_type="rom_control_logic",
num_outputs=(self.cols + self.words_per_row * precharge_cell_effort) \
+ (addr_control_buffer_effort * self.col_bits),
clk_fanout=(self.row_bits * addr_control_buffer_effort) + (precharge_cell_effort * self.rows),
height=self.column_decode.height )
self.bitline_inv = factory.create(module_type="rom_wordline_driver_array",
module_name="rom_bitline_inverter",
rows=self.cols,
fanout=4,
invert_outputs=True,
tap_spacing=0,
flip_io=True)
self.output_inv = factory.create(module_type="rom_wordline_driver_array",
module_name="rom_output_buffer",
rows=self.word_size,
fanout=4,
tap_spacing=1,
invert_outputs=True)
def create_instances(self):
gnd = ["gnd"]
vdd = ["vdd"]
prechrg = ["precharge"]
clk = ["clk_int"]
bitlines = ["bl_{}".format(bl) for bl in range(self.cols)]
wordlines = ["wl_{}".format(wl) for wl in range(self.rows)]
addr_msb = ["addr[{}]".format(addr + self.col_bits) for addr in range(self.row_bits)]
addr_lsb = ["addr[{}]".format(addr) for addr in range(self.col_bits)]
select_lines = ["word_sel_{}".format(word) for word in range(self.words_per_row)]
bitline_bar = ["bl_b_{}".format(bl) for bl in range(self.cols)]
pre_buf_outputs = ["rom_out_prebuf_{}".format(bit) for bit in range(self.word_size)]
outputs = ["dout[{}]".format(bl) for bl in range(self.word_size)]
array_pins = bitlines + wordlines + prechrg + vdd + gnd
row_decode_pins = addr_msb + wordlines + clk + clk + vdd + gnd
col_decode_pins = addr_lsb + select_lines + prechrg + prechrg + vdd + gnd
col_mux_pins = bitline_bar + select_lines + pre_buf_outputs + gnd
bitline_inv_pins = bitlines + bitline_bar + vdd + gnd
output_buf_pins = pre_buf_outputs + outputs + vdd + gnd
self.array_inst = self.add_inst(name="rom_bit_array", mod=self.array)
self.connect_inst(array_pins)
self.decode_inst = self.add_inst(name="rom_row_decoder", mod=self.decode_array)
self.connect_inst(row_decode_pins)
self.control_inst = self.add_inst(name="rom_control", mod=self.control_logic)
self.connect_inst(["clk", "cs", "precharge", "clk_int", "vdd", "gnd"])
self.mux_inst = self.add_inst(name="rom_column_mux", mod=self.column_mux)
self.connect_inst(col_mux_pins)
self.col_decode_inst = self.add_inst(name="rom_column_decoder", mod=self.column_decode)
self.connect_inst(col_decode_pins)
self.bitline_inv_inst = self.add_inst(name="rom_bitline_inverter", mod=self.bitline_inv)
self.connect_inst(bitline_inv_pins)
self.output_inv_inst = self.add_inst(name="rom_output_inverter", mod=self.output_inv)
self.connect_inst(output_buf_pins)
def place_instances(self):
self.place_row_decoder()
self.place_data_array()
self.place_bitline_inverter()
self.place_col_mux()
self.place_col_decoder()
self.place_control_logic()
self.place_output_buffer()
def place_row_decoder(self):
self.decode_offset = vector(0, self.control_inst.height )
self.decode_inst.place(offset=self.decode_offset)
def place_data_array(self):
# We approximate the correct position for the array
array_x = self.decode_inst.width + (2) * ( self.route_layer_width + self.route_layer_pitch )
array_y = self.decode_array.buf_inst.height - self.array.precharge_inst.cy() - self.array.zero_cell.height * 0.5
self.array_offset = vector(array_x ,array_y)
self.array_inst.place(offset=self.array_offset)
# now move array to correct alignment with decoder
array_align = self.decode_inst.get_pin("wl_0").cy() - self.array_inst.get_pin("wl_0_0").cy()
self.array_inst.place(offset=(self.array_offset + vector(0, array_align)))
def place_bitline_inverter(self):
self.bitline_inv_inst.place(offset=[0,0], rotate=90)
inv_y_offset = self.array_inst.by() - self.bitline_inv_inst.width - 2 * self.m1_pitch
inv_x_offset = self.array_inst.get_pin("bl_0_0").cx() - self.bitline_inv_inst.get_pin("out_0").cx()
self.inv_offset = vector(inv_x_offset, inv_y_offset)
self.bitline_inv_inst.place(offset=self.inv_offset, rotate=90)
def place_control_logic(self):
self.control_offset = vector(self.col_decode_inst.lx() - self.control_inst.width - 3 * self.m1_pitch, self.decode_inst.by() - self.control_logic.height - self.m1_pitch)
self.control_inst.place(offset=self.control_offset)
def place_col_decoder(self):
col_decode_y = self.mux_inst.get_pin("sel_0").cy() - self.col_decode_inst.get_pin("wl_0").cy()
self.col_decode_offset = vector(self.decode_inst.width - self.col_decode_inst.width, col_decode_y)
self.col_decode_inst.place(offset=self.col_decode_offset)
def place_col_mux(self):
mux_y_offset = self.bitline_inv_inst.by() - self.mux_inst.height - 5 * self.route_layer_pitch
mux_x_offset = self.bitline_inv_inst.get_pin("out_0").cx() - self.mux_inst.get_pin("bl_0").cx()
self.mux_offset = vector(mux_x_offset, mux_y_offset)
self.mux_inst.place(offset=self.mux_offset)
def place_output_buffer(self):
output_x = self.col_decode_inst.rx() + self.output_inv_inst.height
output_y = self.mux_inst.by() - self.word_size * self.m1_pitch
self.output_inv_offset = vector(output_x, output_y)
self.output_inv_inst.place(offset=self.output_inv_offset, rotate=270)
def route_decode_outputs(self):
# for the row decoder
route_pins = [self.array_inst.get_pin("wl_0_{}".format(wl)) for wl in range(self.rows)]
decode_pins = [self.decode_inst.get_pin("wl_{}".format(wl)) for wl in range(self.rows)]
route_pins.extend(decode_pins)
self.connect_row_pins(self.interconnect_layer, route_pins, round=True)
# then for the column decoder
col_decode_pins = [self.col_decode_inst.get_pin("wl_{}".format(wl)) for wl in range(self.words_per_row)]
sel_pins = [self.mux_inst.get_pin("sel_{}".format(wl)) for wl in range(self.words_per_row)]
sel_pins.extend(col_decode_pins)
self.connect_row_pins(self.wordline_layer, sel_pins, round=True)
def route_array_inputs(self):
for wl in range(self.rows):
array_wl = self.array.wordline_names[0][wl]
array_wl_pin = self.array_inst.get_pin(array_wl)
wl_bus_wire = self.wl_bus[self.wl_interconnects[wl]]
end = array_wl_pin.center()
start = vector(wl_bus_wire.cx(), end.y)
self.add_segment_center(self.interconnect_layer, start, end)
def route_precharge(self):
prechrg_control = self.control_inst.get_pin("prechrg")
col_decode_prechrg = self.col_decode_inst.get_pin("precharge_r")
col_decode_clk = self.col_decode_inst.get_pin("clk")
array_prechrg = self.array_inst.get_pin("precharge")
self.add_via_stack_center(from_layer=self.route_stack[0],
to_layer=prechrg_control.layer,
offset=prechrg_control.center())
# Route precharge to col decoder
start = prechrg_control.center()
mid1 = vector(self.control_inst.rx() + self.interconnect_layer_pitch, prechrg_control.cy())
mid2 = vector(self.control_inst.rx() + self.interconnect_layer_pitch, col_decode_prechrg.cy())
end = col_decode_prechrg.center()
self.add_path(self.route_stack[0], [start, mid1, mid2, end])
self.add_via_stack_center(from_layer=self.route_stack[0],
to_layer=col_decode_prechrg.layer,
offset=end)
start = mid1
mid1 = vector(self.control_inst.rx() + self.interconnect_layer_pitch, start.y)
mid2 = vector(mid1.x, col_decode_clk.cy())
end = col_decode_clk.center()
self.add_path(self.route_stack[0], [start, mid1, mid2, end])
# Route precharge to main array
mid = vector(col_decode_prechrg.cx(), array_prechrg.cy() )
self.add_path(self.route_stack[0], [array_prechrg.center(), mid, col_decode_prechrg.center()])
def route_clock(self):
clk_out = self.control_inst.get_pin("clk_out")
row_decode_clk = self.decode_inst.get_pin("clk")
self.add_via_stack_center(from_layer=self.route_stack[2],
to_layer=clk_out.layer,
offset=clk_out.center())
# Route clock to row decoder
mid = vector(self.control_inst.rx() + self.m1_pitch, clk_out.cy())
addr_control_clk = row_decode_clk.rc() + vector( 2 * self.route_layer_pitch + self.route_layer_width, 0)
row_decode_prechrg = self.decode_inst.get_pin("precharge")
self.add_path(self.route_stack[2], [clk_out.center(), mid, addr_control_clk, row_decode_prechrg.center()])
self.add_via_stack_center(from_layer=self.route_stack[2],
to_layer=row_decode_clk.layer,
offset=addr_control_clk)
self.add_segment_center(row_decode_clk.layer, addr_control_clk, row_decode_clk.rc())
def route_array_outputs(self):
array_out_pins = [self.array_inst.get_pin("bl_0_{}".format(bl)) for bl in range(self.cols)]
inv_in_pins = [self.bitline_inv_inst.get_pin("in_{}".format(bl)) for bl in range(self.cols)]
inv_out_pins = [self.bitline_inv_inst.get_pin("out_{}".format(bl)) for bl in range(self.cols)]
mux_pins = [self.mux_inst.get_pin("bl_{}".format(bl)) for bl in range(self.cols)]
self.connect_col_pins(self.interconnect_layer, array_out_pins + inv_in_pins, round=True, directions="nonpref")
self.connect_col_pins(self.interconnect_layer, inv_out_pins + mux_pins, round=True, directions="nonpref")
def route_output_buffers(self):
mux = self.mux_inst
buf = self.output_inv_inst
route_nets = [ [mux.get_pin("bl_out_{}".format(bit)), buf.get_pin("in_{}".format(bit))] for bit in range(self.word_size)]
channel_ll = vector( route_nets[0][0].cx(), route_nets[0][1].cy() + self.m1_pitch * 3)
self.create_horizontal_channel_route(netlist=route_nets, offset=channel_ll, layer_stack=self.m1_stack)
def place_top_level_pins(self):
self.add_io_pin(self.control_inst, "CS", "cs")
self.add_io_pin(self.control_inst, "clk_in", "clk")
for i in range(self.word_size):
self.add_io_pin(self.output_inv_inst, "out_{}".format(i), "dout[{}]".format(i), directions="nonpref")
for lsb in range(self.col_bits):
name = "addr[{}]".format(lsb)
self.add_io_pin(self.col_decode_inst, "A{}".format(lsb), name)
for msb in range(self.col_bits, self.row_bits + self.col_bits):
name = "addr[{}]".format(msb)
pin_num = msb - self.col_bits
self.add_io_pin(self.decode_inst, "A{}".format(pin_num), name)
def route_supplies(self, bbox=None):
for pin_name in ["vdd", "gnd"]:
for inst in self.insts:
self.copy_power_pins(inst, pin_name)
if not OPTS.route_supplies:
# Do not route the power supply (leave as must-connect pins)
return
elif OPTS.route_supplies == "grid":
from openram.router import supply_grid_router as router
else:
from openram.router import supply_tree_router as router
rtr=router(layers=self.supply_stack,
design=self,
bbox=bbox,
pin_type=OPTS.supply_pin_type)
rtr.route()
if OPTS.supply_pin_type in ["left", "right", "top", "bottom", "ring"]:
# Find the lowest leftest pin for vdd and gnd
for pin_name in ["vdd", "gnd"]:
# Copy the pin shape(s) to rectangles
for pin in self.get_pins(pin_name):
self.add_rect(layer=pin.layer,
offset=pin.ll(),
width=pin.width(),
height=pin.height())
# Remove the pin shape(s)
self.remove_layout_pin(pin_name)
# Get new pins
pins = rtr.get_new_pins(pin_name)
for pin in pins:
self.add_layout_pin(pin_name,
pin.layer,
pin.ll(),
pin.width(),
pin.height())
def route_escape_pins(self, bbox):
pins_to_route = []
for bit in range(self.col_bits):
pins_to_route.append("addr[{0}]".format(bit))
for bit in range(self.row_bits):
pins_to_route.append("addr[{0}]".format(bit + self.col_bits))
for bit in range(self.word_size):
pins_to_route.append("dout[{0}]".format(bit))
pins_to_route.append("clk")
pins_to_route.append("cs")
from openram.router import signal_escape_router as router
rtr=router(layers=self.m3_stack,
design=self,
bbox=bbox)
rtr.escape_route(pins_to_route)
+377
View File
@@ -0,0 +1,377 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 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 math
from .bitcell_base_array import bitcell_base_array
from openram.base import vector
from openram import OPTS, debug
from openram.sram_factory import factory
from openram.tech import drc, layer
class rom_base_array(bitcell_base_array):
def __init__(self, rows, cols, strap_spacing, bitmap, tap_spacing = 4, name="", bitline_layer="m1", wordline_layer="m2", tap_direction="row", pitch_match=False):
super().__init__(name=name, rows=rows, cols=cols, column_offset=0)
self.data = bitmap
self.tap_direction = tap_direction
self.pitch_match = pitch_match
self.bitline_layer = bitline_layer
self.strap_spacing = strap_spacing
self.wordline_layer = wordline_layer
self.data_col_size = self.column_size
self.tap_spacing = tap_spacing
if strap_spacing != 0:
self.array_col_size = self.column_size + math.ceil(self.column_size / strap_spacing)
else:
self.array_col_size = self.column_size
self.create_all_bitline_names()
self.create_all_wordline_names()
# debug.info(1, "ROM array with rows: {0}, cols: {1}".format(self.row_size, self.column_size))
self.create_netlist()
self.create_layout()
def create_netlist(self):
self.add_modules()
self.add_pins()
self.create_cell_instances()
self.create_precharge_inst()
def create_layout(self):
self.create_layout_constants()
self.place_array()
if self.tap_direction == "row":
self.route_pitch_offsets()
self.place_precharge()
self.place_wordline_contacts()
self.place_bitline_contacts()
self.route_precharge()
self.add_boundary()
self.route_supplies()
self.connect_taps()
def add_boundary(self):
ll = self.find_lowest_coords()
m1_offset = self.m1_width
self.translate_all(vector(0, ll.y + 0.5 * m1_offset))
ur = self.find_highest_coords()
ur = vector(ur.x, ur.y - self.m1_width)
super().add_boundary(vector(0, 0), ur)
self.width = ur.x
self.height = ur.y
def add_modules(self):
self.zero_cell = factory.create(module_name="rom_base_zero_cell",
module_type="rom_base_cell",
bitline_layer=self.bitline_layer,
bit_value=0)
self.one_cell = factory.create(module_name="rom_base_one_cell",
module_type="rom_base_cell",
bitline_layer=self.bitline_layer,
bit_value=1)
if self.tap_direction == "row":
self.poly_tap = factory.create(module_type="rom_poly_tap")
else:
self.poly_tap = factory.create(module_type="rom_poly_tap", add_active_tap=True)
self.end_poly_tap = factory.create(module_type="rom_poly_tap", place_poly=True)
self.precharge_array = factory.create(module_type="rom_precharge_array",
cols=self.column_size,
strap_spacing=self.strap_spacing,
bitline_layer=self.bitline_layer,
strap_layer=self.wordline_layer,
tap_direction=self.tap_direction)
def create_layout_constants(self):
self.route_width = drc("minwidth_" + self.bitline_layer)
self.route_pitch = drc("{0}_to_{0}".format(self.bitline_layer))
def add_pins(self):
for bl_name in self.get_bitline_names():
self.add_pin(bl_name, "OUTPUT")
for wl_name in self.get_wordline_names():
self.add_pin(wl_name, "INPUT")
self.add_pin("precharge", "INPUT")
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
def create_cell_instances(self):
self.tap_inst = {}
self.active_tap_list = []
self.poly_tap_list = []
self.cell_inst = {}
self.cell_list = []
self.current_row = 0
# list of current bitline interconnect nets,
# starts as the same as the bitline list and is updated when new insts of cells are added
self.int_bl_list = self.bitline_names[0].copy()
for row in range(self.row_size + 1):
row_list = []
for col in range(self.column_size):
if col % self.strap_spacing == 0:
self.create_poly_tap(row, col)
new_inst = self.create_cell(row, col)
self.cell_inst[row, col] = new_inst
row_list.append(new_inst)
self.create_poly_tap(row, self.column_size)
# name = "tap_r{0}_c{1}".format(row, self.array_col_size)
# new_tap = self.add_inst(name=name, mod=self.poly_tap)
# self.tap_inst[row, self.column_size] = new_tap
# self.tap_list.append(new_tap)
# self.connect_inst([])
self.cell_list.append(row_list)
def create_poly_tap(self, row, col):
name = "tap_r{0}_c{1}".format(row, col)
if row == self.row_size and self.tap_direction == "col":
new_tap = self.add_inst(name=name, mod=self.end_poly_tap)
else:
new_tap = self.add_inst(name=name, mod=self.poly_tap)
self.active_tap_list.append(new_tap)
self.tap_inst[row, col]=new_tap
self.poly_tap_list.append(new_tap)
self.connect_inst([])
def create_cell(self, row, col):
name = "bit_r{0}_c{1}".format(row, col)
# when col = 0, bl_h is connected to precharge, otherwise connect to previous bl connection
# when col = col_size - 1 connected column_sizeto gnd otherwise create new bl connection
# debug.info(1, "Create cell: r{0}, c{1}".format(row, col))
if row == self.row_size:
bl_l = self.int_bl_list[col]
bl_h = "gnd"
else:
bl_l = self.int_bl_list[col]
if self.data[row][col] == 1:
self.int_bl_list[col] = "bl_int_{0}_{1}".format(row, col)
bl_h = self.int_bl_list[col]
# Final row of dummy nmos that contains only 1s, acts to prevent shorting bl to ground when precharging
if row == self.row_size:
new_inst = self.add_inst(name=name, mod=self.one_cell)
self.connect_inst([bl_h, bl_l, "precharge", "gnd"])
elif self.data[row][col] == 1:
new_inst = self.add_inst(name=name, mod=self.one_cell)
self.connect_inst([bl_h, bl_l, self.wordline_names[0][row], "gnd"])
else:
new_inst = self.add_inst(name=name, mod=self.zero_cell)
self.connect_inst([bl_h, self.wordline_names[0][row], "gnd"])
return new_inst
def create_precharge_inst(self):
prechrg_pins = self.bitline_names[0].copy()
prechrg_pins.append("precharge")
prechrg_pins.append("vdd")
self.precharge_inst = self.add_inst(name="bitcell_array_precharge", mod=self.precharge_array)
self.connect_inst(prechrg_pins)
def create_all_bitline_names(self):
for col in range(self.column_size):
for port in self.all_ports:
self.bitline_names[port].extend(["bl_{0}_{1}".format(port, col)])
# Make a flat list too
self.all_bitline_names = [x for sl in zip(*self.bitline_names) for x in sl]
def route_supplies(self):
via_width = drc("m2_enclose_via1") * 0.5 + drc("minwidth_via1")
pitch = drc["{0}_to_{0}".format(self.wordline_layer)]
drain_l = self.cell_list[self.row_size][0].get_pin("D")
drain_r = self.cell_list[self.row_size][self.column_size - 1].get_pin("D")
gnd_l = drain_l.center() + vector(-0.5 * self.route_width, pitch + via_width + self.route_pitch)
gnd_r = drain_r.center() + vector(0.5 * self.route_width, pitch + via_width + self.route_pitch)
self.add_layout_pin_rect_ends(name="gnd", layer=self.bitline_layer, start=gnd_l, end=gnd_r)
if self.tap_direction == "row":
self.connect_row_pins(layer=self.wordline_layer, pins=self.gnd_taps, name="gnd")
self.remove_layout_pin("gnd_tap")
if self.tap_direction == "col":
self.remove_layout_pin("gnd")
active_tap_pins = [self.active_tap_list[i].get_pin("active_tap") for i in range(len(self.active_tap_list))]
self.connect_col_pins(layer=self.supply_stack[0], pins=active_tap_pins, name="gnd_tmp")
gnd_y = gnd_l.y
min_x = float('inf')
max_x = 0
for pin in self.get_pins("gnd_tmp"):
# find the pins on the edges
if pin.cx() < min_x:
min_x = pin.cx()
if pin.cx() > max_x:
max_x = pin.cx()
bottom = vector(pin.cx(), pin.by())
top = vector(pin.cx(), gnd_y)
self.add_via_stack_center(offset=top, from_layer=self.bitline_layer, to_layer=self.supply_stack[0])
self.add_via_center(offset=bottom, layers=self.supply_stack)
self.add_layout_pin_rect_ends(name="gnd", layer=self.supply_stack[0], start=bottom, end=top)
self.remove_layout_pin("gnd_tmp")
self.add_segment_center(layer=self.supply_stack[2], start=vector(min_x, bottom.y), end=vector(max_x, bottom.y))
self.add_segment_center(layer=self.bitline_layer, start=gnd_l, end=vector(min_x, gnd_l.y))
self.add_segment_center(layer=self.bitline_layer, start=gnd_r, end=vector(max_x, gnd_r.y))
self.copy_layout_pin(self.precharge_inst, "vdd")
def place_array(self):
self.cell_pos = {}
self.strap_pos = {}
pitch_offset = 0
for row in range(self.row_size + 1):
if row % self.tap_spacing == 0 and self.pitch_match and row != self.row_size:
pitch_offset += self.active_contact.width + self.active_space
cell_y = row * (self.zero_cell.height) + pitch_offset
cell_x = 0
for col in range(self.column_size):
if col % self.strap_spacing == 0:
self.strap_pos[row, col] = vector(cell_x, cell_y)
self.tap_inst[row, col].place(self.strap_pos[row, col])
if self.tap_direction == "col":
cell_x += self.poly_tap.pitch_offset
self.cell_pos[row, col] = vector(cell_x, cell_y)
self.cell_inst[row, col].place(self.cell_pos[row, col])
cell_x += self.zero_cell.width
self.strap_pos[row, self.column_size] = vector(cell_x, cell_y)
self.tap_inst[row, self.column_size].place(self.strap_pos[row, self.column_size])
def route_pitch_offsets(self):
self.gnd_taps = []
for row in range(0 , self.row_size, self.tap_spacing):
for col in range(self.column_size):
cell = self.cell_inst[row, col]
source = cell.get_pin("S")
if row != 0:
drain = self.cell_inst[row - 1, col].get_pin("D")
start = vector(drain.cx(), source.cy())
end = drain.center()
self.add_segment_center(self.bitline_layer, start, end)
self.place_well_tap(row, col)
def place_well_tap(self, row, col):
cell = self.cell_inst[row, col]
source = cell.get_pin("S")
if col != self.column_size - 1:
tap_x = (self.cell_inst[row , col + 1].get_pin("S").cx() + source.cx()) * 0.5
else:
tap_x = cell.rx() + self.active_space
if row != 0:
drain = self.cell_inst[row - 1, col].get_pin("D")
tap_y = (source.cy() + drain.cy()) * 0.5
else:
tap_y = source.cy() - self.contact_width - 2 * self.active_enclose_contact - self.active_space
tap_pos = vector(tap_x, tap_y)
self.add_via_center(layers=self.active_stack,
offset=tap_pos,
implant_type="p",
well_type="p",
directions="nonpref")
self.add_via_stack_center(offset=tap_pos,
from_layer=self.active_stack[2],
to_layer=self.wordline_layer)
self.gnd_taps.append(self.add_layout_pin_rect_center("gnd_tap", self.wordline_layer, tap_pos))
def place_precharge(self):
self.precharge_offset = vector(0, - self.precharge_inst.height - self.zero_cell.nmos.end_to_contact - 2 * drc["nwell_enclose_active"] - 3 * self.m1_pitch)
self.precharge_inst.place(offset=self.precharge_offset)
self.copy_layout_pin(self.precharge_inst, "gate", "precharge")
def place_wordline_contacts(self):
for wl in range(self.row_size):
self.copy_layout_pin(self.tap_inst[wl, 0], "poly_tap", self.wordline_names[0][wl])
def place_bitline_contacts(self):
rail_y = self.precharge_inst.get_pins("vdd")[0].cy()
for bl in range(self.column_size):
src_pin = self.cell_list[0][bl].get_pin("S")
prechg_pin_name = "pre_bl{0}_out".format(bl)
pre_pin = self.precharge_inst.get_pin(prechg_pin_name)
middle_offset = (src_pin.cy() - pre_pin.cy() ) * 0.5
corrected = vector(src_pin.cx(), src_pin.cy() - middle_offset)
output_pos = vector(corrected.x, rail_y)
self.add_segment_center(self.bitline_layer, corrected, output_pos)
self.add_layout_pin_rect_center(self.bitline_names[0][bl], self.bitline_layer, output_pos )
def route_precharge(self):
for bl in range(self.column_size):
bl_pin = self.cell_list[0][bl].get_pin("S")
prechg_pin = "pre_bl{0}_out".format(bl)
pre_out_pin = self.precharge_inst.get_pin(prechg_pin)
bl_start = bl_pin.center()
bl_end = vector(bl_start.x, pre_out_pin.cy())
self.add_segment_center(self.bitline_layer, bl_start, bl_end)
upper_precharge = self.precharge_inst.get_pin("precharge_r")
lower_precharge = self.tap_inst[self.row_size, self.column_size ].get_pin("poly_tap")
if self.pitch_match:
wire_offset = 2 * self.m1_pitch
else:
wire_offset = 3 * self.m1_pitch
start = upper_precharge.center()
end = lower_precharge.center()
mid1 = start + vector(wire_offset, 0)
mid2 = end + vector(wire_offset, 0)
self.add_path(layer="m1", coordinates=[start, mid1, mid2, end])
self.add_layout_pin_rect_center(text="precharge_r", layer="m1", offset=mid1)
def connect_taps(self):
poly_tap_pins = [self.poly_tap_list[i].get_pin("poly_tap") for i in range(len(self.poly_tap_list))]
self.connect_row_pins(layer=self.wordline_layer, pins=poly_tap_pins)
+125
View File
@@ -0,0 +1,125 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 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.
#
from openram.base import design
from openram.base import vector
from openram import OPTS
from openram.sram_factory import factory
from openram.tech import drc
class rom_base_cell(design):
def __init__(self, name="", bitline_layer="li", bit_value=1, add_well=False):
super().__init__(name)
self.bit_value = bit_value
self.bitline_layer = bitline_layer
self.add_well=add_well
self.create_netlist()
self.create_layout()
def create_netlist(self):
self.add_pins()
self.add_modules()
def create_layout(self):
self.create_tx()
self.setup_drc_offsets()
self.add_boundary()
self.place_tx()
self.place_bitline()
self.place_poly()
if self.bit_value == 0:
self.short_gate()
# Calculates offsets of cell width and height so that tiling of cells does not violate any drc rules
def setup_drc_offsets(self):
self.poly_size = (self.cell_inst.width + self.active_space) - (self.cell_inst.height + 2 * self.poly_extend_active)
def add_boundary(self):
height = self.cell_inst.width + self.active_space
#cell width with offsets applied, height becomes width when the cells are rotated
width = self.cell_inst.height + 2 * self.poly_extend_active
# make the cells square so the pitch of wordlines will match bitlines
if width > height:
self.width = width
self.height = width
else:
self.width = height
self.height = height
super().add_boundary()
def add_modules(self):
self.nmos = factory.create(module_type="ptx",
module_name="nmos_rom_mod",
tx_type="nmos",
add_source_contact=self.bitline_layer,
add_drain_contact=self.bitline_layer
)
def create_tx(self):
self.cell_inst = self.add_inst( name=self.name + "_nmos",
mod=self.nmos,
)
if self.bit_value == 0:
self.connect_inst(["bl", "wl", "bl", "gnd"])
else:
self.connect_inst(["bl_h", "wl", "bl_l", "gnd"])
def add_pins(self):
if self.bit_value == 0 :
pin_list = ["bl", "wl", "gnd"]
dir_list = ["INOUT", "INPUT", "GROUND"]
else:
pin_list = ["bl_h", "bl_l", "wl", "gnd"]
dir_list = ["INOUT", "INOUT", "INPUT", "GROUND"]
self.add_pin_list(pin_list, dir_list)
def place_tx(self):
# sizing_offset = self.cell_inst.height - drc["minwidth_tx"]
tx_offset = vector(self.poly_extend_active + self.cell_inst.height + self.poly_size,- 0.5 * self.contact_width - self.active_enclose_contact)
# add rect of poly to account for offset from drc spacing
# self.add_rect_center("poly", poly_offset, self.poly_extend_active_spacing, self.poly_width)
self.cell_inst.place(tx_offset, rotate=90)
self.copy_layout_pin(self.cell_inst, "S", "S")
self.copy_layout_pin(self.cell_inst, "D", "D")
self.source_pos = self.cell_inst.get_pin("S").center()
def place_poly(self):
poly_offset = vector(0, self.cell_inst.width * 0.5 - 0.5 * self.contact_width - self.active_enclose_contact)
start = poly_offset
end = poly_offset + vector(self.poly_size, 0)
self.add_segment_center("poly", start, end)
def place_bitline(self):
start = self.get_pin("D").center()
end = start + vector(0, 2 * self.active_enclose_contact + 0.5 * self.contact_width + self.active_space)
self.add_segment_center(self.bitline_layer, start, end)
def short_gate(self):
self.add_segment_center(self.bitline_layer, self.get_pin("D").center(), self.get_pin("S").center())
+173
View File
@@ -0,0 +1,173 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 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.
#
from openram import debug
from openram.base import vector
from openram.sram_factory import factory
from openram.tech import drc, layer
from openram.tech import cell_properties as cell_props
from openram import OPTS
from .pgate import *
class rom_column_mux(pgate):
"""
This module implements the columnmux bitline cell used in the design.
Creates a single column mux cell with the given integer size relative
to minimum size. Default is 8x. Per Samira and Hodges-Jackson book:
Column-mux transistors driven by the decoder must be sized
for optimal speed
"""
def __init__(self, name, tx_size=8, input_layer="m2", output_layer="m1"):
debug.info(2, "creating single ROM column mux cell: {0}".format(name))
self.tx_size = int(tx_size)
self.input_layer = input_layer
self.output_layer= output_layer
super().__init__(name)
def create_netlist(self):
self.add_pins()
self.add_ptx()
def create_layout(self):
self.pin_layer = self.input_layer
self.pin_pitch = getattr(self, "{}_pitch".format(self.pin_layer))
self.pin_width = getattr(self, "{}_width".format(self.pin_layer))
self.pin_height = 2 * self.pin_width
# If li exists, use li and m1 for the mux, otherwise use m1 and m2
if self.output_layer == "li" :
self.col_mux_stack = self.li_stack
else:
self.col_mux_stack = self.m1_stack
self.place_ptx()
self.width = self.bitcell.width
self.height = self.nmos_lower.uy() + self.pin_height
self.connect_poly()
self.add_bitline_pins()
self.connect_bitlines()
self.add_pn_wells()
def add_ptx(self):
self.bitcell = factory.create(module_type="rom_base_cell")
# Adds nmos_lower,nmos_upper to the module
self.ptx_width = self.tx_size * drc("minwidth_tx")
self.nmos = factory.create(module_type="ptx",
width=self.ptx_width)
# Space it in the center
self.nmos_lower = self.add_inst(name="mux_tx1",
mod=self.nmos)
self.connect_inst(["bl", "sel", "bl_out", "gnd"])
def add_pins(self):
self.add_pin_list(["bl", "bl_out", "sel", "gnd"])
def add_bitline_pins(self):
""" Add the top and bottom pins to this cell """
bl_pos = vector(self.pin_pitch, 0)
# bl and br
self.add_layout_pin(text="bl",
layer=self.pin_layer,
offset=bl_pos + vector(0, self.height - self.pin_height),
height=self.pin_height)
# bl_out and br_out
self.add_layout_pin(text="bl_out",
layer=self.col_mux_stack[0],
offset=bl_pos,
height=self.pin_height)
def place_ptx(self):
""" Create the pass gate NMOS transistor to switch the bitline """
# Space it in the center
nmos_lower_position = self.nmos.active_offset.scale(0, 1) \
+ vector(0.5 * self.bitcell.width- 0.5 * self.nmos.active_width, 0)
self.nmos_lower.place(nmos_lower_position)
def connect_poly(self):
""" Connect the poly gate of the two pass transistors """
# offset is the top of the lower nmos' diffusion
# height is the distance between the nmos' diffusions, which depends on max(self.active_space,self.poly_space)
offset = self.nmos_lower.get_pin("G").ul() - vector(0, self.poly_extend_active)
height = self.poly_extend_active - offset.y
self.add_rect(layer="poly",
offset=offset,
height=height)
# Add the sel pin to the bottom of the mux
self.add_layout_pin(text="sel",
layer="poly",
offset=self.nmos_lower.get_pin("G").ll(),
height=self.poly_extend_active)
def connect_bitlines(self):
""" Connect the bitlines to the mux transistors """
bl_pin = self.get_pin("bl")
bl_out_pin = self.get_pin("bl_out")
nmos_lower_s_pin = self.nmos_lower.get_pin("S")
nmos_lower_d_pin = self.nmos_lower.get_pin("D")
self.add_via_stack_center(from_layer=nmos_lower_s_pin.layer,
to_layer=self.input_layer,
offset=nmos_lower_s_pin.center())
self.add_via_stack_center(from_layer=nmos_lower_d_pin.layer,
to_layer=self.output_layer,
offset=nmos_lower_d_pin.center())
# bl -> nmos_upper/D on metal1
# bl_out -> nmos_upper/S on metal2
mid1 = bl_pin.bc().scale(1, 0.4) \
+ nmos_lower_s_pin.uc().scale(0, 0.5)
mid2 = bl_pin.bc().scale(0, 0.4) \
+ nmos_lower_s_pin.uc().scale(1, 0.5)
self.add_path(self.input_layer,
[bl_pin.bc(), mid1, mid2, nmos_lower_s_pin.center()])
# halfway up, move over
mid1 = bl_out_pin.uc().scale(1, 0.4) \
+ nmos_lower_d_pin.bc().scale(0, 0.4)
mid2 = bl_out_pin.uc().scale(0, 0.4) \
+ nmos_lower_d_pin.bc().scale(1, 0.4)
self.add_path(self.output_layer,
[bl_out_pin.uc(), mid1, mid2, nmos_lower_d_pin.center()])
def add_pn_wells(self):
"""
Add a well and implant over the whole cell. Also, add the
pwell contact (if it exists)
"""
# Add it to the right, aligned in between the two tx
active_pos = vector(self.bitcell.width,
self.nmos_lower.uy() + self.active_contact.height + self.active_space)
self.add_via_center(layers=self.active_stack,
offset=active_pos,
implant_type="p",
well_type="p")
# If there is a li layer, include it in the power stack
self.add_via_stack_center(from_layer=self.active_stack[2],
to_layer=self.pin_layer,
offset=active_pos)
self.add_layout_pin_rect_center(text="gnd",
layer=self.pin_layer,
offset=active_pos)
+209
View File
@@ -0,0 +1,209 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 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.
#
from openram import debug
from openram.base import design
from openram.base import vector
from openram.sram_factory import factory
from openram.tech import layer, preferred_directions, drc
from openram.tech import layer_properties as layer_props
from openram import OPTS
class rom_column_mux_array(design):
"""
Dynamically generated column mux array.
Array of column mux to read the bitlines from ROM, based on the RAM column mux
"""
def __init__(self, name, columns, word_size, tap_spacing=4, input_layer="m1", bitline_layer="m1", sel_layer="m2"):
super().__init__(name)
debug.info(1, "Creating {0}".format(self.name))
self.add_comment("cols: {0} word_size: {1} ".format(columns, word_size))
self.columns = columns
self.word_size = word_size
self.words_per_row = int(self.columns / self.word_size)
self.input_layer = input_layer
self.tap_spacing = tap_spacing
self.sel_layer = sel_layer
self.supply_layer = "m2"
self.sel_pitch = getattr(self, self.sel_layer + "_pitch")
self.bitline_layer = bitline_layer
if preferred_directions[self.sel_layer] == "V":
self.via_directions = ("H", "H")
else:
self.via_directions = "pref"
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_modules()
self.add_pins()
self.create_array()
def create_layout(self):
self.setup_layout_constants()
self.place_array()
self.add_routing()
# Find the highest shapes to determine height before adding well
highest = self.find_highest_coords()
self.height = highest.y
self.add_layout_pins()
if "pwell" in layer:
self.add_enclosure(self.mux_inst, "pwell")
self.add_boundary()
self.DRC_LVS()
def add_pins(self):
for i in range(self.columns):
self.add_pin("bl_{}".format(i))
for i in range(self.words_per_row):
self.add_pin("sel_{}".format(i))
for i in range(self.word_size):
self.add_pin("bl_out_{}".format(i))
self.add_pin("gnd")
def add_modules(self):
self.mux = factory.create(module_type="rom_column_mux", input_layer=self.input_layer, output_layer=self.bitline_layer)
self.tap = factory.create(module_type="rom_poly_tap", add_active_tap=True)
self.cell = factory.create(module_type="rom_base_cell")
def setup_layout_constants(self):
self.column_addr_size = int(self.words_per_row / 2)
self.width = self.columns * self.mux.width
# one set of metal1 routes for select signals and a pair to interconnect the mux outputs bl/br
# one extra route pitch is to space from the sense amp
self.route_height = (self.words_per_row + 3) * self.cell.width
self.route_layer_width = drc["minwidth_{}".format(self.bitline_layer)]
self.route_layer_pitch = drc["{0}_to_{0}".format(self.bitline_layer)]
def create_array(self):
self.mux_inst = []
# For every column, add a pass gate
for col_num in range(self.columns):
name = "XMUX{0}".format(col_num)
self.mux_inst.append(self.add_inst(name=name,
mod=self.mux))
self.connect_inst(["bl_{}".format(col_num),
"bl_out_{}".format(int(col_num / self.words_per_row)),
"sel_{}".format(col_num % self.words_per_row),
"gnd"])
def place_array(self):
# Default to single spaced columns
self.offsets = [n * self.mux.width for n in range(self.columns)]
# For every column, add a pass gate
for col_num, xoffset in enumerate(self.offsets[0:self.columns]):
offset = vector(xoffset, self.route_height)
self.mux_inst[col_num].place(offset=offset)
def add_layout_pins(self):
""" Add the pins after we determine the height. """
# For every column, add a pass gate
for col_num in range(self.columns):
mux_inst = self.mux_inst[col_num]
bl_pin = mux_inst.get_pin("bl")
offset = bl_pin.ll()
self.add_layout_pin(text="bl_{}".format(col_num),
layer=bl_pin.layer,
offset=offset,
height=self.height - offset.y)
def route_supplies(self):
self.route_horizontal_pins("gnd", self.insts, layer=self.supply_layer)
def add_routing(self):
self.add_horizontal_input_rail()
self.add_vertical_poly_rail()
self.route_bitlines()
self.route_supplies()
def add_horizontal_input_rail(self):
""" Create address input rails below the mux transistors """
tap_offset = 0
for j in range(self.words_per_row):
if j % self.tap_spacing == 0 and j != 0:
tap_offset += self.tap.pitch_offset
offset = vector(0, self.route_height + tap_offset + (j - self.words_per_row) * self.cell.width)
self.add_layout_pin(text="sel_{}".format(j),
layer=self.sel_layer,
offset=offset,
width=self.mux_inst[-1].rx())
def add_vertical_poly_rail(self):
""" Connect the poly to the address rails """
# Offset to the first transistor gate in the pass gate
for col in range(self.columns):
# which select bit should this column connect to depends on the position in the word
sel_index = col % self.words_per_row
# Add the column x offset to find the right select bit
gate_offset = self.mux_inst[col].get_pin("sel").bc()
# use the y offset from the sel pin and the x offset from the gate
offset = vector(gate_offset.x,
self.get_pin("sel_{}".format(sel_index)).cy())
bl_x_offset = self.mux_inst[col].get_pin("bl_out").cx() + 2 * self.route_layer_width + self.route_layer_pitch + 0.5 * self.poly_contact.width
bl_offset = offset.scale(0, 1) + vector(bl_x_offset, 0)
self.add_via_stack_center(from_layer="poly",
to_layer=self.sel_layer,
offset=bl_offset,
directions=self.via_directions)
self.add_path("poly", [offset, gate_offset, bl_offset])
def route_bitlines(self):
""" Connect the output bit-lines to form the appropriate width mux """
for j in range(self.columns):
bl_offset_begin = self.mux_inst[j].get_pin("bl_out").bc()
bl_out_offset_begin = bl_offset_begin - vector(0, (self.words_per_row + 1) * self.cell.width)
# Add the horizontal wires for the first bit
if j % self.words_per_row == 0:
bl_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("bl_out").bc()
bl_out_offset_end = bl_offset_end - vector(0, (self.words_per_row + 1) * self.cell.width)
self.add_path(self.sel_layer, [bl_out_offset_begin, bl_out_offset_end])
# Extend the bitline output rails and gnd downward on the first bit of each n-way mux
self.add_layout_pin_segment_center(text="bl_out_{}".format(int(j / self.words_per_row)),
layer=self.bitline_layer,
start=bl_offset_begin,
end=bl_out_offset_begin)
else:
self.add_path(self.bitline_layer, [bl_out_offset_begin, bl_offset_begin])
# This via is on the right of the wire
self.add_via_stack_center(from_layer=self.bitline_layer,
to_layer=self.sel_layer,
offset=bl_out_offset_begin,
directions=self.via_directions)
def graph_exclude_columns(self, column_include_num):
"""
Excludes all columns muxes unrelated to the target bit being simulated.
Each mux in mux_inst corresponds to respective column in bitcell array.
"""
for i in range(len(self.mux_inst)):
if i != column_include_num:
self.graph_inst_exclude.add(self.mux_inst[i])
+141
View File
@@ -0,0 +1,141 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 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.
#
from openram.sram_factory import factory
from openram.base import vector, design
from openram.tech import layer, drc
class rom_control_logic(design):
def __init__(self, num_outputs, clk_fanout, name="", height=None):
self.output_size = num_outputs
super().__init__(name, prop=False)
self.height = height
if self.height is not None:
self.driver_height = 0.5 * self.height
self.gate_height = 0.5 * self.height
else:
self.gate_height = 20 * self.m1_pitch
self.driver_height = self.gate_height
self.clk_fanout = clk_fanout
if "li" in layer:
self.route_stack = self.li_stack
else:
self.route_stack = self.m1_stack
self.create_netlist()
self.create_layout()
self.add_boundary()
def create_netlist(self):
self.add_modules()
self.add_pins()
def create_layout(self):
self.create_instances()
self.height=self.driver_inst.height + self.buf_inst.height
self.width= max(self.nand_inst.width + self.buf_inst.width, self.driver_inst.width)
self.place_instances()
self.route_insts()
def add_modules(self):
self.buf_mod = factory.create(module_type="pdriver",
module_name="rom_clock_driver",
height=self.gate_height,
fanout=self.clk_fanout + 2,
add_wells=True,
)
self.nand_mod = factory.create(module_type="pnand2",
module_name="rom_control_nand",
height=self.gate_height,
add_wells=False)
self.driver_mod = factory.create(module_type="pdriver",
module_name="rom_precharge_driver",
inverting=True,
fanout=self.output_size,
height=self.driver_height,
add_wells=True)
def add_pins(self):
self.add_pin("clk_in", "INPUT")
self.add_pin("CS", "INPUT")
self.add_pin("prechrg", "OUTPUT")
self.add_pin("clk_out", "OUTPUT")
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
def create_instances(self):
self.buf_inst = self.add_inst(name="clk_driver", mod=self.buf_mod)
self.connect_inst(["clk_in", "clk_out", "vdd", "gnd"])
self.nand_inst = self.add_inst(name="control_nand", mod=self.nand_mod)
self.connect_inst(["CS", "clk_out", "pre_drive", "vdd", "gnd"])
self.driver_inst = self.add_inst(name="precharge_driver", mod=self.driver_mod)
self.connect_inst(["pre_drive", "prechrg", "vdd", "gnd"])
def place_instances(self):
self.nand_inst.place(offset=[self.buf_inst.width, 0])
self.driver_inst.place(offset=[0, self.buf_inst.height + self.driver_inst.height], mirror="MX")
# hack to get around the fact these modules dont tile properly
offset = self.driver_inst.get_pin("vdd").cy() - self.nand_inst.get_pin("vdd").cy()
self.driver_inst.place(offset=[0, self.buf_inst.height + self.driver_inst.height - offset], mirror="MX")
def route_insts(self):
route_width = drc["minwidth_{}".format(self.route_stack[2])]
self.copy_layout_pin(self.buf_inst, "A", "clk_in")
self.copy_layout_pin(self.buf_inst, "Z", "clk_out")
self.copy_layout_pin(self.driver_inst, "Z", "prechrg")
self.copy_layout_pin(self.nand_inst, "A", "CS")
self.copy_power_pin(self.buf_inst.get_pin("gnd"), directions="nonpref")
self.copy_power_pin(self.driver_inst.get_pin("gnd"), directions="nonpref")
self.copy_power_pin(self.buf_inst.get_pin("vdd"), directions="nonpref")
clk = self.buf_inst.get_pin("Z")
nand_B = self.nand_inst.get_pin("B")
# Connect buffered clock bar to nand input
mid = vector(clk.lx() - route_width - 2 * self.m1_space)
self.add_path(self.route_stack[2], [clk.center(), nand_B.center()])
self.add_via_stack_center(from_layer=clk.layer,
to_layer=self.route_stack[2],
offset=clk.center())
self.add_via_stack_center(from_layer=nand_B.layer,
to_layer=self.route_stack[2],
offset=nand_B.center())
# Connect nand output to precharge driver
nand_Z = self.nand_inst.get_pin("Z")
driver_A = self.driver_inst.get_pin("A")
mid = vector(driver_A.cx(), driver_A.cy() - 4 * route_width)
self.add_path(self.route_stack[2], [nand_Z.center(), mid, driver_A.center()])
self.add_via_stack_center(from_layer=nand_Z.layer,
to_layer=self.route_stack[2],
offset=nand_Z.center())
self.add_via_stack_center(from_layer=driver_A.layer,
to_layer=self.route_stack[2],
offset=driver_A.center())
+243
View File
@@ -0,0 +1,243 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 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.
#
from math import ceil, log
from openram.sram_factory import factory
from openram.base import vector, design
from openram import OPTS
from openram.tech import drc
class rom_decoder(design):
def __init__(self, num_outputs, fanout, strap_spacing, name="", route_layer="m1", output_layer="m1", invert_outputs=False):
# word lines in the base array become the address lines/cols in the decoder
# bit lines in the base array become the word lines/rows in the decoder
# array gets rotated 90deg so rows/cols switch
self.strap_spacing=strap_spacing
self.num_outputs = num_outputs
self.num_inputs = ceil(log(num_outputs, 2))
self.create_decode_map()
super().__init__(name)
b = factory.create(module_type=OPTS.bitcell)
self.cell_height = b.height
self.route_layer = route_layer
self.output_layer = output_layer
self.inv_route_layer = "m2"
self.fanout=fanout
self.invert_outputs=invert_outputs
self.create_netlist()
self.width = self.array_mod.height + self.wordline_buf.width
self.height = self.array_mod.width + self.control_array.height
self.create_layout()
def create_netlist(self):
self.add_modules()
self.add_pins()
self.create_instances()
def create_layout(self):
self.setup_layout_constants()
self.place_array()
self.place_input_buffer()
self.place_driver()
self.route_outputs()
self.connect_inputs()
self.route_supplies()
self.add_boundary()
def add_boundary(self):
ll = self.find_lowest_coords()
m1_offset = self.m1_width
self.translate_all(vector(0, ll.y))
ur = self.find_highest_coords()
ur = vector(ur.x, ur.y)
super().add_boundary(ll, ur)
self.width = ur.x
self.height = ur.y
def setup_layout_constants(self):
self.inv_route_width = drc["minwidth_{}".format(self.inv_route_layer)]
def create_decode_map(self):
self.decode_map = []
# create decoding map that will be the bitmap for the rom decoder
# row/col order in the map will be switched in the placed decoder/
for col in range(self.num_inputs):
# odd cols are address
# even cols are address bar
col_array = []
inv_col_array = []
for row in range(self.num_outputs):
addr_idx = -col - 1
addr = format(row, 'b')
if col >= len(addr) :
bin_digit = 0
else:
bin_digit = int(addr[addr_idx])
col_array.append(bin_digit)
if bin_digit == 0 : inv_col_array.append(1)
else : inv_col_array.append(0)
self.decode_map.append(col_array)
self.decode_map.append(inv_col_array)
self.decode_map.reverse()
def add_pins(self):
for i in range(self.num_inputs):
self.add_pin("A{0}".format(i), "INPUT")
for j in range(self.num_outputs):
self.add_pin("wl_{0}".format(j), "OUTPUT")
self.add_pin("precharge", "INPUT")
self.add_pin("clk", "INPUT")
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
def add_modules(self):
self.control_array = factory.create(module_type="rom_address_control_array",
cols=self.num_inputs)
self.wordline_buf = factory.create(module_type="rom_wordline_driver_array", module_name="{}_wordline_buffer".format(self.name),
rows=self.num_outputs,
fanout=ceil(self.fanout),
invert_outputs=self.invert_outputs,
tap_spacing=self.strap_spacing)
self.array_mod = factory.create(module_type="rom_base_array",
module_name="{}_array".format(self.name),
cols=self.num_outputs,
rows=2 * self.num_inputs,
bitmap=self.decode_map,
strap_spacing = self.strap_spacing,
bitline_layer=self.output_layer,
tap_direction="col")
def create_instances(self):
self.create_array_inst()
self.create_input_buffer()
self.create_wordline_buffer()
def create_input_buffer(self):
name = "pre_control_array"
self.buf_inst = self.add_inst(name=name, mod=self.control_array)
control_pins = []
for i in range(self.num_inputs):
control_pins.append("A{0}".format(i))
for i in range(self.num_inputs):
control_pins.append("A_int_{0}".format(i))
for i in range(self.num_inputs):
control_pins.append("Ab_int_{0}".format(i))
control_pins.append("clk")
control_pins.append("vdd")
control_pins.append("gnd")
self.connect_inst(control_pins)
def create_array_inst(self):
self.array_inst = self.add_inst(name="decode_array_inst", mod=self.array_mod)
array_pins = []
for j in range(self.num_outputs):
name = "wl_int{0}".format(j)
array_pins.append(name)
for i in reversed(range(self.num_inputs)):
array_pins.append("Ab_int_{0}".format(i))
array_pins.append("A_int_{0}".format(i))
array_pins.append("precharge")
array_pins.append("vdd")
array_pins.append("gnd")
self.connect_inst(array_pins)
def create_wordline_buffer(self):
self.wordline_buf_inst = self.add_inst("rom_wordline_driver", mod=self.wordline_buf)
in_pins = ["wl_int{}".format(wl) for wl in range(self.num_outputs)]
out_pins = ["wl_{}".format(wl) for wl in range(self.num_outputs)]
pwr_pins = ["vdd", "gnd"]
self.connect_inst(in_pins + out_pins + pwr_pins)
def place_input_buffer(self):
wl = self.array_mod.row_size - 1
align = self.array_inst.get_pin(self.array_mod.wordline_names[0][wl]).cx() - self.buf_inst.get_pin("A0_out").cx()
self.buf_inst.place(vector(align, 0))
self.copy_layout_pin(self.buf_inst, "clk")
def place_array(self):
offset = vector(self.array_mod.height, self.control_array.height + self.m1_width + self.poly_contact.width)
self.array_inst.place(offset, rotate=90)
def place_driver(self):
offset = vector(self.array_inst.height + self.m1_width, self.array_inst.by())
self.wordline_buf_inst.place(offset)
# calculate the offset between the decode array and the buffer inputs now that their zeros are aligned
pin_offset = self.array_inst.get_pin("bl_0_0").cy() - self.wordline_buf_inst.get_pin("in_0").cy()
self.wordline_buf_inst.place(offset + vector(0, pin_offset))
def route_outputs(self):
for j in range(self.num_outputs):
self.copy_layout_pin(self.wordline_buf_inst, "out_{}".format(j), "wl_{}".format(j))
offset = self.wordline_buf_inst.get_pin("out_{}".format(j)).center()
array_pins = [self.array_inst.get_pin("bl_0_{}".format(bl)) for bl in range(self.num_outputs)]
driver_pins = [self.wordline_buf_inst.get_pin("in_{}".format(bl)) for bl in range(self.num_outputs)]
route_pins = array_pins + driver_pins
self.connect_row_pins(self.output_layer, route_pins, round=True)
def connect_inputs(self):
self.copy_layout_pin(self.array_inst, "precharge")
self.copy_layout_pin(self.array_inst, "precharge_r")
for i in range(self.num_inputs):
wl = (self.num_inputs - i) * 2 - 1
wl_bar = wl - 1
addr_pin = self.array_inst.get_pin(self.array_mod.wordline_names[0][wl])
addr_bar_pin = self.array_inst.get_pin(self.array_mod.wordline_names[0][wl_bar])
addr_out_pin = self.buf_inst.get_pin("A{}_out".format(i))
addr_bar_out_pin = self.buf_inst.get_pin("Abar{}_out".format(i))
addr_middle = vector(addr_pin.cx(), addr_out_pin.cy())
addr_bar_middle = vector(addr_bar_pin.cx(), addr_bar_out_pin.cy())
self.add_path(self.inv_route_layer, [addr_out_pin.center(), addr_middle, addr_pin.center()])
self.add_path(self.inv_route_layer, [addr_bar_out_pin.center(), addr_bar_middle, addr_bar_pin.center()])
self.copy_layout_pin(self.buf_inst, "A{}_in".format(i), "A{}".format(i))
def route_supplies(self):
self.copy_layout_pin(self.array_inst, "vdd")
self.copy_layout_pin(self.wordline_buf_inst, "vdd")
self.copy_layout_pin(self.buf_inst, "vdd")
self.copy_layout_pin(self.array_inst, "gnd")
self.copy_layout_pin(self.wordline_buf_inst, "gnd")
self.copy_layout_pin(self.buf_inst, "gnd")
+96
View File
@@ -0,0 +1,96 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 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.
#
from openram.base import design
from openram.base import vector
from openram import OPTS
from openram.sram_factory import factory
from openram.tech import drc
class rom_poly_tap(design):
def __init__(self, name="", cell_name=None, tx_type="nmos", strap_layer="m2", add_active_tap=False, place_poly=None):
super().__init__(name, cell_name)
self.strap_layer=strap_layer
self.tx_type = tx_type
self.add_tap = add_active_tap
if place_poly is None:
self.place_poly = add_active_tap
else:
self.place_poly = place_poly
self.pitch_offset = 0
self.create_netlist()
self.create_layout()
def create_netlist(self):
#for layout constants
self.dummy = factory.create(module_type="rom_base_cell")
self.pmos = factory.create(module_type="ptx", tx_type="pmos")
def create_layout(self):
self.place_via()
self.add_boundary()
if self.add_tap or self.place_poly:
self.place_active_tap()
self.extend_poly()
def add_boundary(self):
contact_width = self.poly_contact.width
self.height = self.dummy.height
self.width = contact_width + self.pitch_offset
super().add_boundary()
def place_via(self):
contact_width = self.poly_contact.width
contact_y = self.dummy.cell_inst.width * 0.5 - 0.5 * self.contact_width - self.active_enclose_contact
self.contact_x_offset = 0
contact_x = contact_width * 0.5 + self.contact_x_offset
self.contact_offset = vector(contact_x, contact_y)
self.via = self.add_via_stack_center(from_layer="poly",
to_layer=self.strap_layer,
offset=self.contact_offset)
self.add_layout_pin_rect_center("poly_tap", self.strap_layer, self.contact_offset)
def extend_poly(self):
y_offset = 0
if self.tx_type == "pmos":
y_offset = -self.height
start = self.via.center() + vector(0, y_offset)
self.add_segment_center("poly", start, vector(self.via.cx() + self.pitch_offset, self.via.cy() + y_offset))
self.add_segment_center("poly", start, vector(0, self.via.cy() + y_offset))
def place_active_tap(self):
gap = self.poly_extend_active - 0.5 * ( self.active_contact.height - self.poly_contact.width )
offset = self.active_space - gap
tap_x = self.via.cx() + offset
tap_y = self.via.cy() + self.dummy.width * 0.5
contact_pos = vector(tap_x, tap_y)
# edge of the next nmos
active_edge = self.dummy.width - self.dummy.cell_inst.height - self.poly_extend_active
# edge of the active contact
tap_edge = tap_x + 0.5 * self.active_contact.height
self.pitch_offset += (self.active_space * 2) - (tap_edge - active_edge) + self.contact_x_offset
if self.tx_type == "nmos" and self.add_tap:
self.add_via_center(layers=self.active_stack,
offset=contact_pos,
implant_type="p",
well_type="p",
directions="nonpref")
self.add_layout_pin_rect_center("active_tap", self.active_stack[2], contact_pos)
+177
View File
@@ -0,0 +1,177 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 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.
#
from math import ceil
from openram.base import geometry
from openram.base import design
from openram.sram_factory import factory
from openram.base import vector
from openram.tech import layer, drc
class rom_precharge_array(design):
"""
An array of inverters to create the inverted address lines for the rom decoder
"""
def __init__(self, cols, name="", bitline_layer=None, strap_spacing=None, strap_layer="m2", tap_direction="row"):
self.cols = cols
self.strap_layer = strap_layer
self.tap_direction = tap_direction
if "li" in layer:
self.supply_layer = "li"
else:
self.supply_layer = "m1"
if bitline_layer is not None:
self.bitline_layer = bitline_layer
else:
self.bitline_layer = self.supply_layer
if name=="":
name = "rom_inv_array_{0}".format(cols)
if strap_spacing != None:
self.strap_spacing = strap_spacing
else:
self.strap_spacing = 0
if strap_spacing != 0:
self.num_straps = ceil(self.cols / self.strap_spacing)
self.array_col_size = self.cols + self.num_straps
else:
self.num_straps = 0
self.array_col_size = self.cols
super().__init__(name)
self.create_netlist()
self.create_layout()
def create_netlist(self):
self.create_modules()
self.add_pins()
self.create_instances()
def create_layout(self):
self.width = self.cols * self.pmos.width
self.height = self.pmos.width
self.place_instances()
self.create_layout_pins()
self.route_supply()
self.connect_taps()
self.add_boundary()
self.extend_well()
def add_boundary(self):
ur = self.find_highest_coords()
self.add_label(layer="nwell", text="upper right",offset=ur)
super().add_boundary(vector(0, 0), ur)
self.height = ur.y
self.width = ur.x
def create_modules(self):
self.pmos = factory.create(module_type="rom_precharge_cell", module_name="precharge_cell", bitline_layer=self.bitline_layer, supply_layer=self.supply_layer)
# For layout constants
self.dummy = factory.create(module_type="rom_base_cell")
self.poly_tap = factory.create(module_type="rom_poly_tap", tx_type="pmos", add_active_tap=(self.tap_direction == "col"))
def add_pins(self):
for col in range(self.cols):
self.add_pin("pre_bl{0}_out".format(col), "OUTPUT")
self.add_pin("gate", "INPUT")
self.add_pin("vdd", "POWER")
def create_instances(self):
self.array_insts = []
self.pmos_insts = []
self.tap_insts = []
self.create_poly_tap(-1)
for col in range(self.cols):
if col % self.strap_spacing == 0:
self.create_poly_tap(col)
self.create_precharge_tx(col)
def create_precharge_tx(self, col):
name = "pmos_c{0}".format(col)
pmos = self.add_inst(name=name, mod=self.pmos)
self.array_insts.append(pmos)
self.pmos_insts.append(pmos)
bl = "pre_bl{0}_out".format(col)
self.connect_inst(["vdd", "gate", bl])
def create_poly_tap(self, col):
name = "tap_c{}".format( col)
new_tap = self.add_inst(name=name, mod=self.poly_tap)
self.tap_insts.append(new_tap)
self.connect_inst([])
def place_instances(self):
self.array_pos = []
strap_num = 0
cell_y = 0
# columns are bit lines
cell_x = 0
for col in range(self.cols):
if col % self.strap_spacing == 0:
self.tap_insts[strap_num].place(vector(cell_x, cell_y + self.poly_tap.height))
strap_num += 1
if self.tap_direction == "col":
cell_x += self.poly_tap.pitch_offset
self.pmos_insts[col].place(vector(cell_x, cell_y))
self.add_label("debug", "li", vector(cell_x, cell_y))
cell_x += self.pmos.width
self.tap_insts[strap_num].place(vector(cell_x, cell_y + self.poly_tap.height))
def create_layout_pins(self):
self.copy_layout_pin(self.tap_insts[0], "poly_tap", "gate")
self.copy_layout_pin(self.tap_insts[-1], "poly_tap", "precharge_r")
for col in range(self.cols):
source_pin = self.pmos_insts[col].get_pin("D")
bl = "pre_bl{0}_out".format(col)
self.add_layout_pin_rect_center(bl, self.bitline_layer, source_pin.center())
def route_supply(self):
self.route_horizontal_pins("vdd", insts=self.pmos_insts, layer=self.strap_layer)
def connect_taps(self):
array_pins = [self.tap_insts[i].get_pin("poly_tap") for i in range(len(self.tap_insts))]
self.connect_row_pins(layer=self.strap_layer, pins=array_pins, name=None, round=False)
for tap in self.tap_insts:
tap_pin = tap.get_pin("poly_tap")
start = vector(tap_pin.cx(), tap_pin.by())
end = vector(start.x, tap.mod.get_pin("poly_tap").cy())
self.add_segment_center(layer="poly", start=start, end=end)
offset_start = vector(end.x - self.poly_tap.width + self.poly_extend_active, end.y)
offset_end = end + vector(0.5*self.poly_width, 0)
self.add_segment_center(layer="poly", start=offset_start, end=offset_end)
def extend_well(self):
self.well_offset = self.pmos.tap_offset
well_y = self.pmos_insts[0].get_pin("vdd").cy() - 0.5 * self.nwell_width
well_y = self.get_pin("vdd").cy() - 0.5 * self.nwell_width
well_ll = vector(0, well_y)
self.add_rect("nwell", well_ll, self.width , self.height - well_y)
+86
View File
@@ -0,0 +1,86 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 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.
#
from .rom_base_cell import rom_base_cell
from .pgate import pgate
from openram.base import vector
from openram import OPTS
from openram.sram_factory import factory
from openram.tech import drc
class rom_precharge_cell(rom_base_cell):
def __init__(self, name="", bitline_layer="m1", supply_layer="li"):
self.supply_layer = supply_layer
super().__init__(name=name, bitline_layer=bitline_layer)
def create_layout(self):
super().create_layout()
self.place_tap()
self.extend_well()
def add_modules(self):
width = pgate.nearest_bin("pmos", drc["minwidth_tx"])
self.pmos = factory.create(module_type="ptx",
module_name="pre_pmos_mod",
tx_type="pmos",
width=width,
add_source_contact=self.supply_layer,
add_drain_contact=self.bitline_layer
)
def create_tx(self):
self.cell_inst = self.add_inst( name="precharge_pmos",
mod=self.pmos,
)
self.connect_inst(["bitline", "gate", "vdd", "vdd"])
def add_pins(self):
pin_list = ["vdd", "gate", "bitline"]
dir_list = ["POWER", "INPUT", "OUTPUT"]
self.add_pin_list(pin_list, dir_list)
def setup_drc_offsets(self):
self.poly_size = (self.cell_inst.width + self.active_space) - (self.cell_inst.height + 2 * self.poly_extend_active)
def extend_well(self):
well_y = self.get_pin("vdd").cy() - 0.5 * self.nwell_width
well_ll = vector(0, well_y)
height = self.get_pin("D").cy() + 0.5 * self.nwell_width - well_y
self.add_rect("nwell", well_ll, self.width , height)
def place_tap(self):
source = self.cell_inst.get_pin("S")
tap_y = source.cy() - self.contact_width - 2 * self.active_enclose_contact - self.active_space
self.tap_offset = abs(tap_y)
pos = vector(source.cx(), tap_y )
self.add_via_center(layers=self.active_stack,
offset=pos,
implant_type="n",
well_type="n",
directions="nonpref")
self.add_via_stack_center(offset=pos,
from_layer=self.active_stack[2],
to_layer=self.supply_layer)
bitline_offset = vector( 1.5 * (drc("minwidth_{}".format(self.bitline_layer)) + drc("{0}_to_{0}".format(self.bitline_layer))) ,0)
self.add_layout_pin_rect_center("vdd", self.supply_layer, pos - bitline_offset)
self.add_path(self.supply_layer, [self.get_pin("vdd").center(), pos, self.get_pin("S").center()])
self.remove_layout_pin("S")
def place_bitline(self):
pass
@@ -0,0 +1,223 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 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.
#
from openram import debug
from openram.base import design, drc
from openram.base import vector
from openram.sram_factory import factory
from openram.tech import layer
from openram.tech import layer_properties as layer_props
from openram import OPTS
class rom_wordline_driver_array(design):
"""
Creates a Wordline Buffer/Inverter array
"""
def __init__(self, name, rows, fanout, invert_outputs=False, tap_spacing=4, flip_io=False):
design.__init__(self, name)
debug.info(1, "Creating {0}".format(self.name))
self.add_comment("rows: {0} Buffer size of: {1}".format(rows, fanout))
self.rows = rows
self.fanout = fanout
self.invert_outputs=invert_outputs
self.tap_spacing = tap_spacing
self.flip_io = flip_io
if OPTS.tech_name == "sky130":
self.supply_layer = "m1"
else:
self.supply_layer = "m2"
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_modules()
self.add_pins()
self.create_drivers()
def create_layout(self):
if "li" in layer:
self.route_layer = "li"
else:
self.route_layer = "m1"
self.place_drivers()
self.route_layout()
if self.tap_spacing != 0:
self.place_taps()
self.route_supplies()
self.add_boundary()
def add_pins(self):
# inputs to wordline_driver.
for i in range(self.rows):
self.add_pin("in_{0}".format(i), "INPUT")
# Outputs from wordline_driver.
for i in range(self.rows):
self.add_pin("out_{0}".format(i), "OUTPUT")
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
def add_modules(self):
b = factory.create(module_type="rom_base_cell")
self.tap = factory.create(module_type="rom_poly_tap", add_active_tap = True)
if self.invert_outputs:
self.wl_driver = factory.create(module_type="pinv_dec",
size=self.fanout,
height=b.height,
add_wells=False,
flip_io=self.flip_io)
else:
self.wl_driver = factory.create(module_type="pbuf_dec",
size=self.fanout,
height=b.height,
add_wells=False)
def route_supplies(self):
"""
Add a pin for each row of vdd/gnd which
are must-connects next level up.
"""
# self.route_vertical_pins("vdd", self.wld_inst, xside="cx", layer=self.supply_layer)
# self.route_vertical_pins("gnd", self.wld_inst, xside="cx", layer=self.supply_layer)
if not self.invert_outputs:
vdd_pins = [pin for inst in self.wld_inst for pin in inst.get_pins("vdd")]
gnd_pins = [pin for inst in self.wld_inst for pin in inst.get_pins("gnd")]
else:
vdd_pins = [inst.get_pin("vdd") for inst in self.wld_inst]
gnd_pins = [inst.get_pin("gnd") for inst in self.wld_inst]
if self.tap_spacing != 0:
vdd_pins = vdd_pins + self.vdd_taps
gnd_pins = gnd_pins + self.gnd_taps
supply_width = drc["minwidth_{}".format(self.supply_layer)]
# Route together all internal supply pins
self.connect_col_pins(layer=self.supply_layer, pins=vdd_pins, name="vdd_tmp")
self.connect_col_pins(layer=self.supply_layer, pins=gnd_pins, name="gnd_tmp")
self.remove_layout_pin("gnd_tap")
self.remove_layout_pin("vdd_tap")
# Place the top level supply pins on the edge of the module
for pin in self.get_pins("gnd_tmp"):
bottom = vector(pin.cx(), pin.by() - 0.5 * supply_width)
top = vector(pin.cx(), pin.uy() + 0.5 * supply_width)
self.add_layout_pin_rect_ends(layer=self.supply_layer, start=bottom, end=top, name="gnd")
for pin in self.get_pins("vdd_tmp"):
bottom = vector(pin.cx(), pin.by() - 0.5 * supply_width)
top = vector(pin.cx(), pin.uy() + 0.5 * supply_width)
self.add_layout_pin_rect_ends(layer=self.supply_layer, start=bottom, end=top, name="vdd")
self.remove_layout_pin("gnd_tmp")
self.remove_layout_pin("vdd_tmp")
def create_drivers(self):
self.wld_inst = []
for row in range(self.rows):
self.wld_inst.append(self.add_inst(name="wld{0}".format(row),
mod=self.wl_driver))
self.connect_inst(["in_{0}".format(row),
"out_{0}".format(row),
"vdd", "gnd"])
def place_drivers(self):
y_offset = 0
for row in range(self.rows):
if self.tap_spacing != 0 and row % self.tap_spacing == 0:
y_offset += self.tap.pitch_offset
offset = [0, y_offset]
self.wld_inst[row].place(offset=offset)
y_offset += self.wld_inst[row].height
self.width = self.wl_driver.width
self.height = self.wl_driver.height * self.rows
def route_layout(self):
""" Route all of the signals """
route_width = drc["minwidth_{}".format(self.route_layer)]
for row in range(self.rows):
if self.flip_io:
row_num = self.rows - row - 1
else:
row_num = row
inst = self.wld_inst[row_num]
self.copy_layout_pin(inst, "A", "in_{0}".format(row))
out_pin = inst.get_pin("Z")
# output each WL on the right
if self.flip_io:
wl_offset = out_pin.lc() - vector(1.6 * route_width, 0)
else:
wl_offset = out_pin.rc() - vector( 0.5 * route_width, 0)
end = vector(wl_offset.x, \
self.get_pin("in_{}".format(row)).cy() + 0.5 * route_width)
self.add_segment_center(layer=self.route_layer,
start=wl_offset,
end=end)
if self.flip_io:
self.add_segment_center(layer=self.route_layer,
start=out_pin.lc(),
end=vector(wl_offset.x - 0.5 * route_width, out_pin.cy()))
self.add_layout_pin_rect_center(text="out_{}".format(row), layer=self.route_layer, offset=end - vector(0, 0.5 * route_width))
def place_taps(self):
self.vdd_taps = []
self.gnd_taps = []
for wl in range(0 , self.rows, self.tap_spacing):
driver = self.wld_inst[wl]
source_pin1 = driver.get_pins("vdd")[0]
gnd_pin1 = driver.get_pins("gnd")[0]
left_edge = driver.get_pin("Z").cy() - 0.5 * self.contact_width - self.active_enclose_contact - self.active_space - 0.5 * self.active_contact.width
contact_pos = vector(source_pin1.cx(), left_edge)
self.place_tap(contact_pos, "n")
contact_pos = vector( gnd_pin1.cx(), left_edge)
self.place_tap(contact_pos, "p")
if not self.invert_outputs:
source_pin2 = driver.get_pins("vdd")[1]
gnd_pin2 = driver.get_pins("gnd")[1]
contact_pos = vector(source_pin2.cx(), left_edge)
self.place_tap(contact_pos, "n")
contact_pos = vector( gnd_pin2.cx(), left_edge)
self.place_tap(contact_pos, "p")
def place_tap(self, offset, well_type):
self.add_via_center(layers=self.active_stack,
offset=offset,
implant_type=well_type,
well_type=well_type,
directions="nonpref")
self.add_via_stack_center(offset=offset,
from_layer=self.active_stack[2],
to_layer=self.supply_layer)
if well_type == "p":
pin = "gnd_tap"
self.gnd_taps.append(self.add_layout_pin_rect_center(text=pin, layer=self.supply_layer, offset=offset))
else:
pin = "vdd_tap"
self.vdd_taps.append(self.add_layout_pin_rect_center(text=pin, layer=self.supply_layer, offset=offset))
@@ -20,3 +20,10 @@ class row_cap_bitcell_1port(bitcell_base):
debug.info(2, "Create row_cap bitcell 1 port object")
self.no_instances = True
def build_graph(self, graph, inst_name, port_nets):
"""
Adds edges based on inputs/outputs.
Overrides base class function.
"""
pass
@@ -20,3 +20,10 @@ class row_cap_bitcell_2port(bitcell_base):
debug.info(2, "Create row_cap bitcell 2 port object")
self.no_instances = True
def build_graph(self, graph, inst_name, port_nets):
"""
Adds edges based on inputs/outputs.
Overrides base class function.
"""
pass