First pass of multiple bitcells per decoder row

This commit is contained in:
mrg 2020-04-10 13:29:41 -07:00
parent 7888e54fc4
commit 2e67d44cd7
3 changed files with 159 additions and 103 deletions

View File

@ -32,11 +32,11 @@ class hierarchical_decoder(design.design):
self.cell_multiple = cell_properties.bitcell.decoder_bitcell_multiple
except AttributeError:
self.cell_multiple = 1
# For debugging
# self.cell_multiple = 2
self.cell_height = self.cell_multiple * b.height
self.num_outputs = num_outputs
# We may have more than one bitcell per decoder row
self.rows = math.ceil(num_outputs / self.cell_multiple)
self.num_inputs = math.ceil(math.log(self.num_outputs, 2))
(self.no_of_pre2x4, self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs)
@ -51,6 +51,10 @@ class hierarchical_decoder(design.design):
because a DRC tool would be required even to run in front-end mode.
"""
b = factory.create(module_type="bitcell")
# Old behavior
if OPTS.netlist_only:
return (b.height, 1)
# Search for the smallest multiple that works
cell_multiple = 1
@ -86,8 +90,8 @@ class hierarchical_decoder(design.design):
self.setup_layout_constants()
self.place_pre_decoder()
self.place_row_decoder()
self.route_input_rails()
self.route_predecode_rails()
self.route_inputs()
self.route_decoder_bus()
self.route_vdd_gnd()
self.offset_all_coordinates()
self.add_boundary()
@ -141,7 +145,7 @@ class hierarchical_decoder(design.design):
def setup_netlist_constants(self):
self.predec_groups = [] # This array is a 2D array.
# Distributing vertical rails to different groups. One group belongs to one pre-decoder.
# Distributing vertical bus to different groups. One group belongs to one pre-decoder.
# For example, for two 2:4 pre-decoder and one 3:8 pre-decoder, we will
# have total 16 output lines out of these 3 pre-decoders and they will
# be distributed as [ [0,1,2,3] ,[4,5,6,7], [8,9,10,11,12,13,14,15] ]
@ -180,39 +184,45 @@ class hierarchical_decoder(design.design):
self.predecoder_height = self.pre2_4.height * self.no_of_pre2x4 + self.pre3_8.height * self.no_of_pre3x8
# We may have more than one bitcell per decoder row
self.num_rows = math.ceil(self.num_outputs / self.cell_multiple)
# We will place this many final decoders per row
self.decoders_per_row = math.ceil(self.num_outputs / self.num_rows)
# Calculates height and width of row-decoder
if (self.num_inputs == 4 or self.num_inputs == 5):
nand_width = self.and2.width
else:
nand_width = self.and3.width
self.internal_routing_width = self.m2_pitch * self.total_number_of_predecoder_outputs
self.row_decoder_height = self.inv.height * self.rows
self.internal_routing_width = self.m2_pitch * (self.total_number_of_predecoder_outputs + 1)
self.row_decoder_height = self.inv.height * self.num_rows
self.input_routing_width = (self.num_inputs + 1) * self.m2_pitch
# Calculates height and width of hierarchical decoder
self.height = self.row_decoder_height
self.height = max(self.predecoder_height, self.row_decoder_height)
self.width = self.input_routing_width + self.predecoder_width \
+ self.internal_routing_width + nand_width + self.inv.width
+ self.internal_routing_width \
+ self.decoders_per_row * nand_width + self.inv.width
def route_input_rails(self):
""" Create input rails for the predecoders """
def route_inputs(self):
""" Create input bus for the predecoders """
# inputs should be as high as the decoders
input_height = self.no_of_pre2x4 * self.pre2_4.height + self.no_of_pre3x8 * self.pre3_8.height
# Find the left-most predecoder
min_x = 0
if self.no_of_pre2x4 > 0:
min_x = min(min_x, -self.pre2_4.width)
min_x = min(min_x, self.pre2x4_inst[0].lx())
if self.no_of_pre3x8 > 0:
min_x = min(min_x, -self.pre3_8.width)
min_x = min(min_x, self.pre3x8_inst[0].lx())
input_offset=vector(min_x - self.input_routing_width, 0)
input_bus_names = ["addr_{0}".format(i) for i in range(self.num_inputs)]
self.input_rails = self.create_vertical_pin_bus(layer="m2",
pitch=self.m2_pitch,
offset=input_offset,
names=input_bus_names,
length=input_height)
self.input_bus = self.create_vertical_pin_bus(layer="m2",
pitch=self.m2_pitch,
offset=input_offset,
names=input_bus_names,
length=input_height)
self.route_input_to_predecodes()
@ -222,7 +232,7 @@ class hierarchical_decoder(design.design):
for i in range(2):
index = pre_num * 2 + i
input_pos = self.input_rails["addr_{}".format(index)]
input_pos = self.input_bus["addr_{}".format(index)]
in_name = "in_{}".format(i)
decoder_pin = self.pre2x4_inst[pre_num].get_pin(in_name)
@ -232,13 +242,13 @@ class hierarchical_decoder(design.design):
decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * self.inv.height)
input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1)
self.route_input_rail(decoder_offset, input_offset)
self.route_input_bus(decoder_offset, input_offset)
for pre_num in range(self.no_of_pre3x8):
for i in range(3):
index = pre_num * 3 + i + self.no_of_pre2x4 * 2
input_pos = self.input_rails["addr_{}".format(index)]
input_pos = self.input_bus["addr_{}".format(index)]
in_name = "in_{}".format(i)
decoder_pin = self.pre3x8_inst[pre_num].get_pin(in_name)
@ -248,9 +258,9 @@ class hierarchical_decoder(design.design):
decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * self.inv.height)
input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1)
self.route_input_rail(decoder_offset, input_offset)
self.route_input_bus(decoder_offset, input_offset)
def route_input_rail(self, input_offset, output_offset):
def route_input_bus(self, input_offset, output_offset):
""" Route a vertical M2 coordinate to another vertical M2 coordinate to the predecode inputs """
self.add_via_center(layers=self.m2_stack,
@ -334,18 +344,17 @@ class hierarchical_decoder(design.design):
else:
base= vector(-self.pre2_4.width, num * self.pre2_4.height)
self.pre2x4_inst[num].place(base)
self.pre2x4_inst[num].place(base - vector(2 * self.m2_pitch, 0))
def place_pre3x8(self, num):
""" Place 3x8 predecoder to the left of the origin and above any 2x4 decoders """
if (self.num_inputs == 3):
offset = vector(-self.pre_3_8.width, 0)
mirror = "R0"
else:
height = self.no_of_pre2x4 * self.pre2_4.height + num * self.pre3_8.height
offset = vector(-self.pre3_8.width, height)
self.pre3x8_inst[num].place(offset)
self.pre3x8_inst[num].place(offset - vector(2 * self.m2_pitch, 0))
def create_row_decoder(self):
""" Create the row-decoder by placing AND2/AND3 and Inverters
@ -362,14 +371,14 @@ class hierarchical_decoder(design.design):
if (self.num_inputs == 4 or self.num_inputs == 5):
for i in range(len(self.predec_groups[0])):
for j in range(len(self.predec_groups[1])):
row = len(self.predec_groups[0]) * j + i
if (row < self.num_outputs):
name = self.AND_FORMAT.format(row)
output = len(self.predec_groups[0]) * j + i
if (output < self.num_outputs):
name = self.AND_FORMAT.format(output)
self.and_inst.append(self.add_inst(name=name,
mod=self.and2))
pins =["out_{0}".format(i),
"out_{0}".format(j + len(self.predec_groups[0])),
"decode_{0}".format(row),
"decode_{0}".format(output),
"vdd", "gnd"]
self.connect_inst(pins)
@ -378,18 +387,18 @@ class hierarchical_decoder(design.design):
for i in range(len(self.predec_groups[0])):
for j in range(len(self.predec_groups[1])):
for k in range(len(self.predec_groups[2])):
row = (len(self.predec_groups[0]) * len(self.predec_groups[1])) * k \
+ len(self.predec_groups[0]) * j + i
output = (len(self.predec_groups[0]) * len(self.predec_groups[1])) * k \
+ len(self.predec_groups[0]) * j + i
if (row < self.num_outputs):
name = self.AND_FORMAT.format(row)
if (output < self.num_outputs):
name = self.AND_FORMAT.format(output)
self.and_inst.append(self.add_inst(name=name,
mod=self.and3))
pins = ["out_{0}".format(i),
"out_{0}".format(j + len(self.predec_groups[0])),
"out_{0}".format(k + len(self.predec_groups[0]) + len(self.predec_groups[1])),
"decode_{0}".format(row),
"decode_{0}".format(output),
"vdd", "gnd"]
self.connect_inst(pins)
@ -403,7 +412,10 @@ class hierarchical_decoder(design.design):
self.route_decoder()
def place_decoder_and_array(self):
""" Add a column of AND gates for final decode """
"""
Add a column of AND gates for final decode.
This may have more than one decoder per row to match the bitcell height.
"""
# Row Decoder AND GATE array for address inputs <5.
if (self.num_inputs == 4 or self.num_inputs == 5):
@ -415,9 +427,13 @@ class hierarchical_decoder(design.design):
self.place_and_array(and_mod=self.and3)
def place_and_array(self, and_mod):
""" Add a column of AND gates for the decoder above the predecoders."""
for row in range(self.num_outputs):
"""
Add a column of AND gates for the decoder above the predecoders.
"""
for inst_index in range(self.num_outputs):
row = math.floor(inst_index / self.decoders_per_row)
dec = inst_index % self.decoders_per_row
if ((row % 2) == 0):
y_off = and_mod.height * row
mirror = "R0"
@ -425,46 +441,52 @@ class hierarchical_decoder(design.design):
y_off = and_mod.height * (row + 1)
mirror = "MX"
self.and_inst[row].place(offset=[self.internal_routing_width, y_off],
mirror=mirror)
x_off = self.internal_routing_width + dec * and_mod.width
self.and_inst[inst_index].place(offset=vector(x_off, y_off),
mirror=mirror)
def route_decoder(self):
""" Add the pins. """
for row in range(self.num_outputs):
z_pin = self.and_inst[row].get_pin("Z")
self.add_layout_pin(text="decode_{0}".format(row),
for output in range(self.num_outputs):
z_pin = self.and_inst[output].get_pin("Z")
self.add_layout_pin(text="decode_{0}".format(output),
layer="m1",
offset=z_pin.ll(),
width=z_pin.width(),
height=z_pin.height())
def route_predecode_rails(self):
""" Creates vertical metal 2 rails to connect predecoder and decoder stages."""
def route_decoder_bus(self):
"""
Creates vertical metal 2 bus to connect predecoder and decoder stages.
"""
# This is not needed for inputs <4 since they have no pre/decode stages.
if (self.num_inputs >= 4):
input_offset = vector(0.5 * self.m2_width, 0)
# This leaves an offset for the predecoder output jogs
input_bus_names = ["predecode_{0}".format(i) for i in range(self.total_number_of_predecoder_outputs)]
self.predecode_rails = self.create_vertical_pin_bus(layer="m2",
pitch=self.m2_pitch,
offset=input_offset,
names=input_bus_names,
length=self.height)
self.predecode_bus = self.create_vertical_pin_bus(layer="m2",
pitch=self.m2_pitch,
offset=vector(0, 0),
names=input_bus_names,
length=self.height)
self.route_rails_to_predecodes()
self.route_rails_to_decoder()
def route_rails_to_predecodes(self):
""" Iterates through all of the predecodes and connects to the rails including the offsets """
self.route_bus_to_predecodes()
self.route_bus_to_decoder()
def route_bus_to_predecodes(self):
"""
Iterates through all of the predecodes
and connects to the rails including the offsets
"""
# FIXME: convert to connect_bus
for pre_num in range(self.no_of_pre2x4):
for i in range(4):
predecode_name = "predecode_{}".format(pre_num * 4 + i)
out_name = "out_{}".format(i)
pin = self.pre2x4_inst[pre_num].get_pin(out_name)
self.route_predecode_rail_m3(predecode_name, pin)
x_offset = self.pre2x4_inst[pre_num].rx() + self.m2_pitch
self.route_predecode_bus_inputs(predecode_name, pin, x_offset)
# FIXME: convert to connect_bus
for pre_num in range(self.no_of_pre3x8):
@ -472,44 +494,68 @@ class hierarchical_decoder(design.design):
predecode_name = "predecode_{}".format(pre_num * 8 + i + self.no_of_pre2x4 * 4)
out_name = "out_{}".format(i)
pin = self.pre3x8_inst[pre_num].get_pin(out_name)
self.route_predecode_rail_m3(predecode_name, pin)
x_offset = self.pre3x8_inst[pre_num].rx() + self.m2_pitch
self.route_predecode_bus_inputs(predecode_name, pin, x_offset)
def route_rails_to_decoder(self):
""" Use the self.predec_groups to determine the connections to the decoder AND gates.
Inputs of AND2/AND3 gates come from different groups.
For example for these groups [ [0,1,2,3] ,[4,5,6,7],
[8,9,10,11,12,13,14,15] ] the first AND3 inputs are connected to
[0,4,8] and second AND3 is connected to [0,4,9] ........... and the
128th AND3 is connected to [3,7,15]
def route_bus_to_decoder(self):
"""
row_index = 0
Use the self.predec_groups to determine the connections to the decoder AND gates.
Inputs of AND2/AND3 gates come from different groups.
For example for these groups
[ [0,1,2,3] ,[4,5,6,7], [8,9,10,11,12,13,14,15] ]
the first AND3 inputs are connected to [0,4,8],
second AND3 is connected to [0,4,9],
...
and the 128th AND3 is connected to [3,7,15]
"""
output_index = 0
if (self.num_inputs == 4 or self.num_inputs == 5):
for index_B in self.predec_groups[1]:
for index_A in self.predec_groups[0]:
# FIXME: convert to connect_bus?
if (row_index < self.num_outputs):
if (output_index < self.num_outputs):
row_index = math.floor(output_index / self.decoders_per_row)
row_remainder = (output_index % self.decoders_per_row)
row_offset = row_index * self.and_inst[0].height + (2 * row_remainder + 4) * self.m1_pitch
predecode_name = "predecode_{}".format(index_A)
self.route_predecode_rail(predecode_name, self.and_inst[row_index].get_pin("A"))
self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("A"),
row_offset)
predecode_name = "predecode_{}".format(index_B)
self.route_predecode_rail(predecode_name, self.and_inst[row_index].get_pin("B"))
row_index = row_index + 1
self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("B"),
row_offset + self.m1_pitch)
output_index = output_index + 1
elif (self.num_inputs > 5):
for index_C in self.predec_groups[2]:
for index_B in self.predec_groups[1]:
for index_A in self.predec_groups[0]:
# FIXME: convert to connect_bus?
if (row_index < self.num_outputs):
if (output_index < self.num_outputs):
row_index = math.floor(output_index / self.decoders_per_row)
row_remainder = (output_index % self.decoders_per_row)
row_offset = row_index * self.and_inst[0].height + (3 * row_remainder + 4) * self.m1_pitch
predecode_name = "predecode_{}".format(index_A)
self.route_predecode_rail(predecode_name, self.and_inst[row_index].get_pin("A"))
self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("A"),
row_offset)
predecode_name = "predecode_{}".format(index_B)
self.route_predecode_rail(predecode_name, self.and_inst[row_index].get_pin("B"))
self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("B"),
row_offset + self.m1_pitch)
predecode_name = "predecode_{}".format(index_C)
self.route_predecode_rail(predecode_name, self.and_inst[row_index].get_pin("C"))
row_index = row_index + 1
self.route_predecode_bus_outputs(predecode_name,
self.and_inst[output_index].get_pin("C"),
row_offset + 2 * self.m1_pitch)
output_index = output_index + 1
def route_vdd_gnd(self):
""" Add a pin for each row of vdd/gnd which are must-connects next level up. """
"""
Add a pin for each row of vdd/gnd which are
must-connects next level up.
"""
# The vias will be placed in the center and right of the cells, respectively.
xoffset = self.and_inst[0].rx()
@ -526,23 +572,42 @@ class hierarchical_decoder(design.design):
self.copy_layout_pin(pre, "vdd")
self.copy_layout_pin(pre, "gnd")
def route_predecode_rail(self, rail_name, pin):
""" Connect the routing rail to the given metal1 pin """
rail_pos = vector(self.predecode_rails[rail_name].x, pin.lc().y)
self.add_path("m1", [rail_pos, pin.lc()])
self.add_via_center(layers=self.m1_stack,
offset=rail_pos)
def route_predecode_bus_outputs(self, rail_name, pin, y_offset):
"""
Connect the routing rail to the given metal1 pin
using a routing track at the given y_offset
"""
pin_pos = pin.center()
# If we have a single decoder per row, we can route on M1
if self.decoders_per_row == 1:
rail_pos = vector(self.predecode_bus[rail_name].x, pin_pos.y)
self.add_path("m1", [rail_pos, pin_pos])
self.add_via_center(layers=self.m1_stack,
offset=rail_pos)
# If not, we must route over the decoder cells on M3
else:
rail_pos = vector(self.predecode_bus[rail_name].x, y_offset)
mid_pos = vector(pin_pos.x, rail_pos.y)
self.add_wire(self.m2_stack[::-1], [rail_pos, mid_pos, pin_pos])
self.add_via_center(layers=self.m2_stack,
offset=rail_pos)
self.add_via_center(layers=self.m1_stack,
offset=pin_pos)
def route_predecode_rail_m3(self, rail_name, pin):
""" Connect the routing rail to the given metal1 pin """
def route_predecode_bus_inputs(self, rail_name, pin, x_offset):
"""
Connect the routing rail to the given metal1 pin using a jog
to the right of the cell at the given x_offset.
"""
# This routes the pin up to the rail, basically, to avoid conflicts.
# It would be fixed with a channel router.
mid_point = vector(pin.cx(), pin.cy() + self.inv.height / 2)
rail_pos = vector(self.predecode_rails[rail_name].x, mid_point.y)
pin_pos = pin.center()
mid_point1 = vector(x_offset, pin_pos.y)
mid_point2 = vector(x_offset, pin_pos.y + self.inv.height / 2)
rail_pos = vector(self.predecode_bus[rail_name].x, mid_point2.y)
self.add_wire(self.m1_stack, [pin_pos, mid_point1, mid_point2, rail_pos])
self.add_via_center(layers=self.m1_stack,
offset=pin.center())
self.add_wire(("m3", "via2", "m2"), [rail_pos, mid_point, pin.uc()])
self.add_via_center(layers=self.m2_stack,
offset=rail_pos)
def input_load(self):

View File

@ -221,7 +221,7 @@ class pnand3(pgate.pgate):
self.nmos3_inst,
self.inputC_yoffset,
"C",
position="center")
position="left")
# FIXME: constant hack
if OPTS.tech_name == "s8":
@ -232,7 +232,7 @@ class pnand3(pgate.pgate):
self.nmos1_inst,
self.inputA_yoffset,
"A",
position="center")
position="left")
def route_output(self):
""" Route the Z output """
@ -255,10 +255,10 @@ class pnand3(pgate.pgate):
directions=("V", "V"))
# PMOS3 and NMOS3 are drain aligned
self.add_path("m2", [pmos3_pin.center(), nmos3_pin.center()])
self.add_path("m1", [pmos3_pin.center(), nmos3_pin.center()])
# Route in the A input track (top track)
mid_offset = vector(nmos3_pin.center().x, self.inputA_yoffset)
self.add_path("m2", [pmos1_pin.center(), mid_offset, nmos3_pin.uc()])
self.add_path("m1", [pmos1_pin.center(), mid_offset, nmos3_pin.uc()])
# This extends the output to the edge of the cell
self.add_via_center(layers=self.m1_stack,

View File

@ -20,15 +20,6 @@ class hierarchical_decoder_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
# Doesn't require hierarchical decoder
# debug.info(1, "Testing 4 row sample for hierarchical_decoder")
# a = hierarchical_decoder.hierarchical_decoder(name="hd1, num_outputs=4)
# self.local_check(a)
# Doesn't require hierarchical decoder
# debug.info(1, "Testing 8 row sample for hierarchical_decoder")
# a = hierarchical_decoder.hierarchical_decoder(name="hd2", num_outputs=8)
# self.local_check(a)
# check hierarchical decoder for single port
debug.info(1, "Testing 16 row sample for hierarchical_decoder")