Rework hierarchical decoder to not be folded. Remove address from central bank bus and access via side pins now. Eight way column mux now works.

This commit is contained in:
Matt Guthaus 2018-03-19 15:11:42 -07:00
parent 1f81b24e96
commit bab92fcf38
9 changed files with 114 additions and 197 deletions

View File

@ -30,6 +30,8 @@ class design(hierarchy_spice.spice, hierarchy_layout.layout):
# These modules ensure unique names or have no changes if they
# aren't unique
ok_list = ['ms_flop.ms_flop',
'dff.dff',
'dff_buf.dff_buf',
'bitcell.bitcell',
'contact.contact',
'ptx.ptx',

View File

@ -91,6 +91,8 @@ class spice(verilog.verilog):
group of modules are generated."""
if (check and (len(self.insts[-1].mod.pins) != len(args))):
debug.error("Connections: {}".format(self.insts[-1].mod.pins))
debug.error("Connections: {}".format(args))
debug.error("Number of net connections ({0}) does not match last instance ({1})".format(len(self.insts[-1].mod.pins),
len(args)), 1)
self.conns.append(args)

View File

@ -161,17 +161,15 @@ class bank(design.design):
# The central bus is the column address (one hot) and row address (binary)
if self.col_addr_size>0:
self.num_col_addr_lines = 2**self.col_addr_size
self.num_addr_lines = self.num_col_addr_lines + self.row_addr_size
else:
self.num_col_addr_lines = 0
self.num_addr_lines = self.row_addr_size
# M1/M2 routing pitch is based on contacted pitch
self.m1_pitch = contact.m1m2.height + max(self.m1_space,self.m2_space)
self.m2_pitch = contact.m2m3.height + max(self.m2_space,self.m3_space)
# The width of this bus is needed to place other modules (e.g. decoder)
self.central_bus_width = self.m2_pitch * (self.num_control_lines + self.num_addr_lines + 1)
self.central_bus_width = self.m2_pitch * self.num_control_lines
@ -362,12 +360,10 @@ class bank(design.design):
# The predecoder is below the x-axis and the main decoder is above the x-axis
# The address flop and decoder are aligned in the x coord.
decoder_x_offset = self.row_decoder.width + self.central_bus_width
offset = vector(decoder_x_offset,
self.row_decoder.predecoder_height)
x_offset = -(self.row_decoder.width + self.central_bus_width + self.wordline_driver.width)
self.row_decoder_inst=self.add_inst(name="row_decoder",
mod=self.row_decoder,
offset=offset.scale(-1,-1))
offset=vector(x_offset,0))
temp = []
for i in range(self.row_addr_size):
@ -381,12 +377,10 @@ class bank(design.design):
""" Wordline Driver """
# The wordline driver is placed to the right of the main decoder width.
# This means that it slightly overlaps with the hierarchical decoder,
# but it shares power rails. This may differ for other decoders later...
x_offset = self.row_decoder.width + self.central_bus_width - self.row_decoder.row_decoder_width
x_offset = -(self.central_bus_width + self.wordline_driver.width) + self.m2_pitch
self.wordline_driver_inst=self.add_inst(name="wordline_driver",
mod=self.wordline_driver,
offset=vector(x_offset,0).scale(-1,-1))
offset=vector(x_offset,0))
temp = []
for i in range(self.num_rows):
@ -404,8 +398,8 @@ class bank(design.design):
Create a 2:4 or 3:8 column address decoder.
"""
# Place the col decoder aligned left to row decoder
x_off = -(self.central_bus_width + self.row_decoder.width)
y_off = -(self.row_decoder.predecoder_height + self.col_decoder.height + 2*drc["well_to_well"])
x_off = -(self.row_decoder.width + self.central_bus_width + self.wordline_driver.width)
y_off = -(self.col_decoder.height + 2*drc["well_to_well"])
self.col_decoder_inst=self.add_inst(name="col_address_decoder",
mod=self.col_decoder,
offset=vector(x_off,y_off))
@ -445,10 +439,10 @@ class bank(design.design):
if not self.num_banks > 1:
return
xoffset = -(self.central_bus_width + self.bank_select.width)
x_off = -(self.row_decoder.width + self.central_bus_width + self.wordline_driver.width)
# extra space to allow vias
yoffset = self.min_point + 2*self.supply_rail_pitch + self.m1_space
self.bank_select_pos = vector(xoffset,yoffset)
y_off = self.min_point + 2*self.supply_rail_pitch + self.m1_space
self.bank_select_pos = vector(x_off,y_off)
self.bank_select_inst = self.add_inst(name="bank_select",
mod=self.bank_select,
offset=self.bank_select_pos)
@ -536,12 +530,11 @@ class bank(design.design):
""" Create the address, supply, and control signal central bus lines. """
# Overall central bus width. It includes all the column mux lines,
# control lines, and address flop to decoder lines.
# and control lines.
# The bank is at (0,0), so this is to the left of the y-axis.
# 2 pitches on the right for vias/jogs to access the inputs
control_bus_x_offset = -self.m2_pitch * (self.num_control_lines)
address_bus_x_offset = control_bus_x_offset - self.m2_pitch*(self.num_addr_lines)
control_bus_x_offset = -self.m2_pitch * self.num_control_lines
# Track the bus offsets for other modules to access
self.bus_xoffset = {}
@ -556,35 +549,6 @@ class bank(design.design):
width=self.m2_width,
height=self.height)
# Row address lines (to left of col address lines)
# goes from bottom of bitcell array down to the bottom of the column decoder/addresses
for i in range(self.row_addr_size):
addr_idx = i + self.col_addr_size
x_offset = address_bus_x_offset + i*self.m2_pitch
name = "A[{}]".format(addr_idx)
# Make the xoffset map the center of the rail
self.bus_xoffset[name]=x_offset + 0.5*self.m2_width
# Add a label pin for LVS correspondence and visual help inspecting the rail.
self.add_layout_pin(text=name,
layer="metal2",
offset=vector(x_offset, self.min_point),
width=self.m2_width,
height=-self.min_point)
# Column mux lines if there is column mux
# goes from bottom of bitcell array down to the bottom of the column decoder/addresses
if self.col_addr_size>0:
for i in range(self.num_col_addr_lines):
x_offset = address_bus_x_offset + (i+self.row_addr_size)*self.m2_pitch
name = "sel[{}]".format(i) # One hot select signals
# Make the xoffset map the center of the rail
self.bus_xoffset[name]=x_offset + 0.5*self.m2_width
# Add a label pin for LVS correspondence
self.add_label_pin(text=name,
layer="metal2",
offset=vector(x_offset, self.col_decoder_inst.by()),
width=self.m2_width,
height=-self.col_decoder_inst.by())
def route_precharge_to_bitcell_array(self):
@ -654,33 +618,14 @@ class bank(design.design):
def route_row_decoder(self):
""" Routes the row decoder inputs and supplies """
for i in range(self.row_addr_size):
addr_idx = i + self.col_addr_size
# before this index, we are using 2x4 decoders
switchover_index = 2*self.row_decoder.no_of_pre2x4
# so decide what modulus to perform the height spacing
if i < switchover_index:
position_heights = i % 2
else:
position_heights = (i-switchover_index) % 3
# Connect the address rails to the decoder
# Note that the decoder inputs are long vertical rails so spread out the connections vertically.
decoder_in_position = self.row_decoder_inst.get_pin("A[{}]".format(i)).lr() \
+ vector(0,position_heights*self.bitcell.height+self.m2_pitch)
rail_position = vector(self.bus_xoffset["A[{}]".format(addr_idx)],
decoder_in_position.y)
self.add_path("metal1",[decoder_in_position,rail_position])
decoder_in_via = decoder_in_position - vector(0,0.5*self.m2_width)
self.add_via(layers=("metal1", "via1", "metal2"),
offset=decoder_in_via,
rotate=90)
# # Create inputs for the row address lines
# for i in range(self.row_addr_size):
# addr_idx = i + self.col_addr_size
# decoder_name = "A[{}]".format(i)
# addr_name = "A[{}]".format(addr_idx)
# self.copy_layout_pin(self.row_decoder_inst, decoder_name, addr_name)
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=rail_position,
rotate=90)
# Route the power and ground, but only BELOW the y=0 since the
# others are connected with the wordline driver.
# These must be on M3 to not interfere with column mux address pins.
@ -730,70 +675,51 @@ class bank(design.design):
def route_column_address_lines(self):
""" Connecting the select lines of column mux to the address bus """
if not self.col_addr_size>0:
return
# Connect the select lines to the column mux
# These must be in metal3 so that they don't overlap any gnd lines from decoders
for i in range(self.num_col_addr_lines):
name = "sel[{}]".format(i)
mux_addr_pos = self.col_mux_array_inst.get_pin(name).lc()
wire_pos = vector(self.bus_xoffset[name], mux_addr_pos.y)
self.add_path("metal1", [wire_pos,mux_addr_pos])
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=wire_pos,
rotate=90)
if self.col_addr_size == 1:
decode_out_pos = self.col_decoder_inst.get_pin("Zb").rc()
selx_pos = vector(self.bus_xoffset["sel[0]"],decode_out_pos.y)
self.add_path("metal1",[decode_out_pos, selx_pos])
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=selx_pos,
rotate=90)
decode_out_pos = self.col_decoder_inst.get_pin("Z").rc()
selx_pos = vector(self.bus_xoffset["sel[1]"],decode_out_pos.y)
self.add_path("metal1",[decode_out_pos, selx_pos])
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=selx_pos,
rotate=90)
# Connect to sel[0] and sel[1]
decode_names = ["Zb", "Z"]
# The Address LSB
decode_in_pin = self.col_decoder_inst.get_pin("A")
pin_pos = vector(decode_in_pin.cx(), self.min_point)
self.add_layout_pin_center_segment(text="A[0]",
layer="metal2",
start=pin_pos,
end=decode_in_pin.bc())
self.copy_layout_pin(self.col_decoder_inst, "A", "A[0]")
elif self.col_addr_size > 1:
# Route the col decoder outputs to the col select bus
decode_names = []
for i in range(self.num_col_addr_lines):
name = "sel[{}]".format(i)
decode_out_pos = self.col_decoder_inst.get_pin("out[{}]".format(i)).rc()
selx_pos = vector(self.bus_xoffset[name],decode_out_pos.y)
self.add_path("metal1",[decode_out_pos, selx_pos])
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=selx_pos,
rotate=90)
decode_names.append("out[{}]".format(i))
# Route from the col decoder up to the address bus
for i in range(self.col_addr_size):
decoder_name = "in[{}]".format(i)
addr_name = "A[{}]".format(i)
decode_in_pin = self.col_decoder_inst.get_pin(decoder_name)
pin_pos = vector(decode_in_pin.cx(), self.min_point)
self.add_layout_pin_center_segment(text=addr_name,
layer="metal2",
start=pin_pos,
end=decode_in_pin.bc())
self.copy_layout_pin(self.col_decoder_inst, decoder_name, addr_name)
# This will do a quick "river route" on two layers.
# When above the top select line it will offset "inward" again to prevent conflicts.
# This could be done on a single layer, but we follow preferred direction rules for later routing.
top_y_offset = self.col_mux_array_inst.get_pin("sel[{}]".format(self.num_col_addr_lines-1)).cy()
for (decode_name,i) in zip(decode_names,range(self.num_col_addr_lines)):
mux_name = "sel[{}]".format(i)
mux_addr_pos = self.col_mux_array_inst.get_pin(mux_name).lc()
decode_out_pos = self.col_decoder_inst.get_pin(decode_name).center()
# To get to the edge of the decoder and one track out
delta_offset = self.col_decoder_inst.rx() - decode_out_pos.x + self.m2_pitch
if decode_out_pos.y > top_y_offset:
mid1_pos = vector(decode_out_pos.x + delta_offset + i*self.m2_pitch,decode_out_pos.y)
else:
mid1_pos = vector(decode_out_pos.x + delta_offset + (self.num_col_addr_lines-i)*self.m2_pitch,decode_out_pos.y)
mid2_pos = vector(mid1_pos.x,mux_addr_pos.y)
self.add_wire(("metal1","via1","metal2"),[decode_out_pos, mid1_pos, mid2_pos, mux_addr_pos])
# route the gnd rails, add contact to rail as well
for gnd_pin in self.col_decoder_inst.get_pins("gnd"):
left_rail_pos = vector(self.left_gnd_x_center, gnd_pin.cy())

View File

@ -154,12 +154,11 @@ class hierarchical_decoder(design.design):
else:
nand_width = self.nand3.width
self.routing_width = self.metal2_pitch*self.total_number_of_predecoder_outputs
self.row_decoder_width = nand_width + self.routing_width + self.inv.width
self.row_decoder_height = self.inv.height * self.rows
# Calculates height and width of hierarchical decoder
self.height = self.row_decoder_height
self.width = self.predecoder_width + self.row_decoder_width
self.width = self.predecoder_width + self.routing_width + nand_width + self.inv.width
def create_pre_decoder(self):
""" Creates pre-decoder and places labels input address [A] """
@ -479,7 +478,7 @@ class hierarchical_decoder(design.design):
def connect_rail_m3(self, rail_index, pin):
""" Connect the routing rail to the given metal1 pin """
mid_point = vector(pin.cx(), pin.cy()-self.inv.height/2)
mid_point = vector(pin.cx(), pin.cy()+self.inv.height/2)
rail_pos = vector(self.rail_x_offsets[rail_index],mid_point.y)
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=pin.center(),

View File

@ -119,7 +119,7 @@ class single_level_column_mux_array(design.design):
def add_horizontal_input_rail(self):
""" Create address input rails on M1 below the mux transistors """
for j in range(self.words_per_row):
offset = vector(0, self.route_height - (j+1)*self.m1_pitch)
offset = vector(0, self.route_height + (j-self.words_per_row)*self.m1_pitch)
self.add_layout_pin(text="sel[{}]".format(j),
layer="metal1",
offset=offset,

View File

@ -22,6 +22,7 @@ class wordline_driver(design.design):
self.rows = rows
self.add_pins()
self.design_layout()
self.offset_all_coordinates()
self.DRC_LVS()
def add_pins(self):

View File

@ -28,7 +28,7 @@ class pinvbuf(design.design):
self.inv2 = pinv(size=inv2_size)
self.add_mod(self.inv2)
self.width = self.inv1.width + self.inv2.width
self.width = 2*self.inv1.width + self.inv2.width
self.height = 2*self.inv1.height
self.create_layout()

View File

@ -20,9 +20,15 @@ class sram(design.design):
c = reload(__import__(OPTS.control_logic))
self.mod_control_logic = getattr(c, OPTS.control_logic)
c = reload(__import__(OPTS.dff_array))
self.mod_dff_array = getattr(c, OPTS.dff_array)
if num_banks>1:
# Use a buffered array for big arrays
# Also ensures we have Qbar when FF doesn't
c = reload(__import__(OPTS.dff_buf_array))
self.mod_dff_array = getattr(c, OPTS.dff_buf_array)
else:
c = reload(__import__(OPTS.dff_array))
self.mod_dff_array = getattr(c, OPTS.dff_array)
c = reload(__import__(OPTS.bitcell))
self.mod_bitcell = getattr(c, OPTS.bitcell)
@ -200,6 +206,32 @@ class sram(design.design):
self.width = self.bank_inst[1].ur().x
self.height = self.control_logic_inst.uy()
def add_single_bank_modules(self):
"""
This adds the moduels for a single bank SRAM with control
logic.
"""
# No orientation or offset
self.bank_inst = self.add_bank(0, [0, 0], 1, 1)
# 3/5/18 MRG: Cannot reference positions inside submodules because boundaries
# are not recomputed using instance placement. So, place the control logic such that it aligns
# with the top of the SRAM.
control_gap = 2*self.m3_width
control_pos = vector(-self.control_logic.width-control_gap,
self.bank.height-self.control_logic.height-3*self.supply_rail_width)
self.add_control_logic(position=control_pos)
# Leave room for the control routes to the left of the flops
addr_pos = vector(self.control_logic_inst.lx() + 4*self.m2_pitch,
3*self.supply_rail_pitch)
self.add_control_addr_dff(addr_pos)
self.width = self.bank.width + self.control_logic.height + control_gap
self.height = self.bank.height
def route_shared_banks(self):
""" Route the shared signals for two and four bank configurations. """
@ -777,9 +809,10 @@ class sram(design.design):
def create_multi_bank_modules(self):
""" Create the multibank address flops and bank decoder """
self.msb_address = self.mod_ms_flop_array(name="msb_address",
columns=self.num_banks/2,
word_size=self.num_banks/2)
self.msb_address = self.mod_dff_array(name="msb_address",
rows=1,
columns=self.num_banks/2)
self.add_mod(self.msb_address)
if self.num_banks>2:
@ -794,9 +827,9 @@ class sram(design.design):
self.add_mod(self.control_logic)
# Create the address and control flops (but not the clk)
dff_size = self.addr_size + len(self.control_logic.get_inputs())-1
self.addr_ctrl_dff = self.mod_dff_array(name="dff_array", rows=dff_size, columns=1)
self.add_mod(self.addr_ctrl_dff)
dff_size = self.addr_size
self.addr_dff = self.mod_dff_array(name="dff_array", rows=dff_size, columns=1)
self.add_mod(self.addr_dff)
# Create the bank module (up to four are instantiated)
self.bank = bank(word_size=self.word_size,
@ -902,9 +935,9 @@ class sram(design.design):
def add_control_addr_dff(self, position):
""" Add and place address and control flops """
self.addr_ctrl_dff_inst = self.add_inst(name="address",
mod=self.addr_ctrl_dff,
offset=position)
self.addr_dff_inst = self.add_inst(name="address",
mod=self.addr_dff,
offset=position)
# inputs, outputs/output/bar
inputs = []
outputs = []
@ -912,12 +945,6 @@ class sram(design.design):
inputs.append("ADDR[{}]".format(i))
outputs.append("A[{}]".format(i))
for i in self.control_logic_inputs:
if i == "clk":
continue
inputs.append(i)
outputs.append(i+"_s")
self.connect_inst(inputs + outputs + ["clk", "vdd", "gnd"])
def add_control_logic(self, position):
@ -951,31 +978,6 @@ class sram(design.design):
layer="metal2",
offset=self.vert_control_bus_positions[n])
def add_single_bank_modules(self):
"""
This adds the moduels for a single bank SRAM with control
logic.
"""
# No orientation or offset
self.bank_inst = self.add_bank(0, [0, 0], 1, 1)
# 3/5/18 MRG: Cannot reference positions inside submodules because boundaries
# are not recomputed using instance placement. So, place the control logic such that it aligns
# with the top of the SRAM.
control_gap = 2*self.m3_width
control_pos = vector(-self.control_logic.width-control_gap,
self.bank.height-self.control_logic.height-3*self.supply_rail_width)
self.add_control_logic(position=control_pos)
# Leave room for the control routes to the left of the flops
addr_pos = vector(self.control_logic_inst.lx() + 4*self.m2_pitch,
3*self.supply_rail_pitch)
self.add_control_addr_dff(addr_pos)
self.width = self.bank.width + self.control_logic.height + control_gap
self.height = self.bank.height
def add_single_bank_pins(self):
"""
Add the top-level pins for a single bank SRAM with control.
@ -985,13 +987,9 @@ class sram(design.design):
self.copy_layout_pin(self.bank_inst, "DATA[{}]".format(i))
for i in range(self.addr_size):
self.copy_layout_pin(self.addr_ctrl_dff_inst, "din[{}]".format(i),"ADDR[{}]".format(i))
self.copy_layout_pin(self.addr_dff_inst, "din[{}]".format(i),"ADDR[{}]".format(i))
ctrl_flops = ["din[{}]".format(i) for i in range(self.addr_size,self.addr_size+3)]
for (old,new) in zip(ctrl_flops,["CSb","WEb","OEb"]):
self.copy_layout_pin(self.addr_ctrl_dff_inst, old, new)
self.copy_layout_pin(self.addr_ctrl_dff_inst, "clk")
self.copy_layout_pin(self.addr_dff_inst, "clk")
# Power ring contains the power pins
@ -1068,7 +1066,7 @@ class sram(design.design):
for i in range(self.addr_size):
flop_name = "dout[{}]".format(i)
bank_name = "A[{}]".format(i)
flop_pin = self.addr_ctrl_dff_inst.get_pin(flop_name)
flop_pin = self.addr_dff_inst.get_pin(flop_name)
bank_pin = self.bank_inst.get_pin(bank_name)
flop_pos = flop_pin.center()
bank_pos = vector(bank_pin.cx(),flop_pos.y)
@ -1080,24 +1078,14 @@ class sram(design.design):
offset=bank_pos,
rotate=90)
# Connect the output of the flops to the control pins
for i in range(3):
flop_name = "dout[{}]".format(self.addr_size+i)
ctrl_name = ["csb","web","oeb"][i]
flop_pin = self.addr_ctrl_dff_inst.get_pin(flop_name)
ctrl_pin = self.control_logic_inst.get_pin(ctrl_name)
flop_pos = flop_pin.center()
ctrl_pos = ctrl_pin.bc()
mid_pos = vector(ctrl_pos.x, flop_pos.y)
self.add_wire(("metal3","via2","metal2"),[flop_pos, mid_pos, ctrl_pos])
self.add_via_center(layers=("metal2","via2","metal3"),
offset=flop_pos,
rotate=90)
# Connect the control pins as inputs
for n in self.control_logic_inputs + ["clk"]:
self.copy_layout_pin(self.control_logic_inst, n.lower(), n)
# Connect the clock between the flops and control module
# FIXME: Buffered clock should drive the flops, but then
# it would change the setup time...
flop_pin = self.addr_ctrl_dff_inst.get_pin("clk")
flop_pin = self.addr_dff_inst.get_pin("clk")
ctrl_pin = self.control_logic_inst.get_pin("clk")
flop_pos = flop_pin.uc()
ctrl_pos = ctrl_pin.bc()
@ -1111,7 +1099,7 @@ class sram(design.design):
""" Route vdd for the control and dff array """
# Route the vdd rails to the LEFT
modules = [ self.control_logic_inst, self.addr_ctrl_dff_inst]
modules = [ self.control_logic_inst, self.addr_dff_inst]
for inst in modules:
for vdd_pin in inst.get_pins("vdd"):
if vdd_pin.layer != "metal1":
@ -1140,7 +1128,7 @@ class sram(design.design):
""" Route gnd for the control and dff array """
# Route the gnd rails to the LEFT
modules = [ self.control_logic_inst, self.addr_ctrl_dff_inst]
modules = [ self.control_logic_inst, self.addr_dff_inst]
for inst in modules:
for gnd_pin in inst.get_pins("gnd"):
if gnd_pin.layer != "metal1":

View File

@ -33,10 +33,9 @@ class multi_bank_test(openram_test):
a = bank.bank(word_size=4, num_words=64, words_per_row=4, num_banks=2, name="bank3")
self.local_check(a)
# Eight way has a short circuit of one column mux select to gnd rail
# debug.info(1, "Eight way column mux")
# a = bank.bank(word_size=2, num_words=128, words_per_row=8, num_banks=2, name="bank4")
# self.local_check(a)
debug.info(1, "Eight way column mux")
a = bank.bank(word_size=2, num_words=128, words_per_row=8, num_banks=2, name="bank4")
self.local_check(a)
OPTS.check_lvsdrc = True
globals.end_openram()