mirror of https://github.com/VLSIDA/OpenRAM.git
Initial refactor of signal and supply router classes.
This commit is contained in:
parent
8f1e2675fe
commit
82833ef8f0
|
|
@ -0,0 +1,874 @@
|
|||
import sys
|
||||
from tech import drc, parameter
|
||||
import debug
|
||||
import design
|
||||
import math
|
||||
from math import log,sqrt,ceil
|
||||
import contact
|
||||
from pinv import pinv
|
||||
from pnand2 import pnand2
|
||||
from pnor2 import pnor2
|
||||
from vector import vector
|
||||
from pinvbuf import pinvbuf
|
||||
|
||||
from globals import OPTS
|
||||
|
||||
class multibank(design.design):
|
||||
"""
|
||||
Dynamically generated a single bank including bitcell array,
|
||||
hierarchical_decoder, precharge, (optional column_mux and column decoder),
|
||||
write driver and sense amplifiers.
|
||||
This module includes the tristate and bank select logic.
|
||||
"""
|
||||
|
||||
def __init__(self, word_size, num_words, words_per_row, num_banks=1, name=""):
|
||||
|
||||
mod_list = ["tri_gate", "bitcell", "decoder", "ms_flop_array", "wordline_driver",
|
||||
"bitcell_array", "sense_amp_array", "precharge_array",
|
||||
"column_mux_array", "write_driver_array", "tri_gate_array",
|
||||
"dff", "bank_select"]
|
||||
from importlib import reload
|
||||
for mod_name in mod_list:
|
||||
config_mod_name = getattr(OPTS, mod_name)
|
||||
class_file = reload(__import__(config_mod_name))
|
||||
mod_class = getattr(class_file , config_mod_name)
|
||||
setattr (self, "mod_"+mod_name, mod_class)
|
||||
|
||||
if name == "":
|
||||
name = "bank_{0}_{1}".format(word_size, num_words)
|
||||
design.design.__init__(self, name)
|
||||
debug.info(2, "create sram of size {0} with {1} words".format(word_size,num_words))
|
||||
|
||||
self.word_size = word_size
|
||||
self.num_words = num_words
|
||||
self.words_per_row = words_per_row
|
||||
self.num_banks = num_banks
|
||||
|
||||
# The local control signals are gated when we have bank select logic,
|
||||
# so this prefix will be added to all of the input signals to create
|
||||
# the internal gated signals.
|
||||
if self.num_banks>1:
|
||||
self.prefix="gated_"
|
||||
else:
|
||||
self.prefix=""
|
||||
|
||||
self.compute_sizes()
|
||||
self.add_pins()
|
||||
self.create_modules()
|
||||
self.add_modules()
|
||||
self.setup_layout_constraints()
|
||||
|
||||
# FIXME: Move this to the add modules function
|
||||
self.add_bank_select()
|
||||
|
||||
self.route_layout()
|
||||
|
||||
|
||||
# Can remove the following, but it helps for debug!
|
||||
self.add_lvs_correspondence_points()
|
||||
|
||||
# Remember the bank center for further placement
|
||||
self.bank_center=self.offset_all_coordinates().scale(-1,-1)
|
||||
|
||||
self.DRC_LVS()
|
||||
|
||||
def add_pins(self):
|
||||
""" Adding pins for Bank module"""
|
||||
for i in range(self.word_size):
|
||||
self.add_pin("DOUT[{0}]".format(i),"OUT")
|
||||
for i in range(self.word_size):
|
||||
self.add_pin("BANK_DIN[{0}]".format(i),"IN")
|
||||
for i in range(self.addr_size):
|
||||
self.add_pin("A[{0}]".format(i),"INPUT")
|
||||
|
||||
# For more than one bank, we have a bank select and name
|
||||
# the signals gated_*.
|
||||
if self.num_banks > 1:
|
||||
self.add_pin("bank_sel","INPUT")
|
||||
for pin in ["s_en","w_en","tri_en_bar","tri_en",
|
||||
"clk_buf_bar","clk_buf"]:
|
||||
self.add_pin(pin,"INPUT")
|
||||
self.add_pin("vdd","POWER")
|
||||
self.add_pin("gnd","GROUND")
|
||||
|
||||
def route_layout(self):
|
||||
""" Create routing amoung the modules """
|
||||
self.route_central_bus()
|
||||
self.route_precharge_to_bitcell_array()
|
||||
self.route_col_mux_to_bitcell_array()
|
||||
self.route_sense_amp_to_col_mux_or_bitcell_array()
|
||||
#self.route_sense_amp_to_trigate()
|
||||
#self.route_tri_gate_out()
|
||||
self.route_sense_amp_out()
|
||||
self.route_wordline_driver()
|
||||
self.route_write_driver()
|
||||
self.route_row_decoder()
|
||||
self.route_column_address_lines()
|
||||
self.route_control_lines()
|
||||
self.add_control_pins()
|
||||
if self.num_banks > 1:
|
||||
self.route_bank_select()
|
||||
|
||||
self.route_vdd_gnd()
|
||||
|
||||
def add_modules(self):
|
||||
""" Add modules. The order should not matter! """
|
||||
|
||||
# Above the bitcell array
|
||||
self.add_bitcell_array()
|
||||
self.add_precharge_array()
|
||||
|
||||
# Below the bitcell array
|
||||
self.add_column_mux_array()
|
||||
self.add_sense_amp_array()
|
||||
self.add_write_driver_array()
|
||||
# Not needed for single bank
|
||||
#self.add_tri_gate_array()
|
||||
|
||||
# To the left of the bitcell array
|
||||
self.add_row_decoder()
|
||||
self.add_wordline_driver()
|
||||
self.add_column_decoder()
|
||||
|
||||
|
||||
|
||||
def compute_sizes(self):
|
||||
""" Computes the required sizes to create the bank """
|
||||
|
||||
self.num_cols = int(self.words_per_row*self.word_size)
|
||||
self.num_rows = int(self.num_words / self.words_per_row)
|
||||
|
||||
self.row_addr_size = int(log(self.num_rows, 2))
|
||||
self.col_addr_size = int(log(self.words_per_row, 2))
|
||||
self.addr_size = self.col_addr_size + self.row_addr_size
|
||||
|
||||
debug.check(self.num_rows*self.num_cols==self.word_size*self.num_words,"Invalid bank sizes.")
|
||||
debug.check(self.addr_size==self.col_addr_size + self.row_addr_size,"Invalid address break down.")
|
||||
|
||||
# Width for the vdd/gnd rails
|
||||
self.supply_rail_width = 4*self.m2_width
|
||||
# FIXME: This spacing should be width dependent...
|
||||
self.supply_rail_pitch = self.supply_rail_width + 4*self.m2_space
|
||||
|
||||
# Number of control lines in the bus
|
||||
self.num_control_lines = 6
|
||||
# The order of the control signals on the control bus:
|
||||
self.input_control_signals = ["clk_buf", "tri_en_bar", "tri_en", "clk_buf_bar", "w_en", "s_en"]
|
||||
# These will be outputs of the gaters if this is multibank, if not, normal signals.
|
||||
if self.num_banks > 1:
|
||||
self.control_signals = ["gated_"+str for str in self.input_control_signals]
|
||||
else:
|
||||
self.control_signals = self.input_control_signals
|
||||
# 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
|
||||
else:
|
||||
self.num_col_addr_lines = 0
|
||||
|
||||
# The width of this bus is needed to place other modules (e.g. decoder)
|
||||
# A width on each side too
|
||||
self.central_bus_width = self.m2_pitch * self.num_control_lines + 2*self.m2_width
|
||||
|
||||
# A space for wells or jogging m2
|
||||
self.m2_gap = max(2*drc["pwell_to_nwell"] + drc["well_enclosure_active"],
|
||||
2*self.m2_pitch)
|
||||
|
||||
|
||||
|
||||
def create_modules(self):
|
||||
""" Create all the modules using the class loader """
|
||||
self.tri = self.mod_tri_gate()
|
||||
self.bitcell = self.mod_bitcell()
|
||||
|
||||
self.bitcell_array = self.mod_bitcell_array(cols=self.num_cols,
|
||||
rows=self.num_rows)
|
||||
self.add_mod(self.bitcell_array)
|
||||
|
||||
self.precharge_array = self.mod_precharge_array(columns=self.num_cols)
|
||||
self.add_mod(self.precharge_array)
|
||||
|
||||
if self.col_addr_size > 0:
|
||||
self.column_mux_array = self.mod_column_mux_array(columns=self.num_cols,
|
||||
word_size=self.word_size)
|
||||
self.add_mod(self.column_mux_array)
|
||||
|
||||
|
||||
self.sense_amp_array = self.mod_sense_amp_array(word_size=self.word_size,
|
||||
words_per_row=self.words_per_row)
|
||||
self.add_mod(self.sense_amp_array)
|
||||
|
||||
self.write_driver_array = self.mod_write_driver_array(columns=self.num_cols,
|
||||
word_size=self.word_size)
|
||||
self.add_mod(self.write_driver_array)
|
||||
|
||||
self.row_decoder = self.mod_decoder(rows=self.num_rows)
|
||||
self.add_mod(self.row_decoder)
|
||||
|
||||
self.tri_gate_array = self.mod_tri_gate_array(columns=self.num_cols,
|
||||
word_size=self.word_size)
|
||||
self.add_mod(self.tri_gate_array)
|
||||
|
||||
self.wordline_driver = self.mod_wordline_driver(rows=self.num_rows)
|
||||
self.add_mod(self.wordline_driver)
|
||||
|
||||
self.inv = pinv()
|
||||
self.add_mod(self.inv)
|
||||
|
||||
if(self.num_banks > 1):
|
||||
self.bank_select = self.mod_bank_select()
|
||||
self.add_mod(self.bank_select)
|
||||
|
||||
|
||||
def add_bitcell_array(self):
|
||||
""" Adding Bitcell Array """
|
||||
|
||||
self.bitcell_array_inst=self.add_inst(name="bitcell_array",
|
||||
mod=self.bitcell_array,
|
||||
offset=vector(0,0))
|
||||
temp = []
|
||||
for i in range(self.num_cols):
|
||||
temp.append("bl[{0}]".format(i))
|
||||
temp.append("br[{0}]".format(i))
|
||||
for j in range(self.num_rows):
|
||||
temp.append("wl[{0}]".format(j))
|
||||
temp.extend(["vdd", "gnd"])
|
||||
self.connect_inst(temp)
|
||||
|
||||
|
||||
def add_precharge_array(self):
|
||||
""" Adding Precharge """
|
||||
|
||||
# The wells must be far enough apart
|
||||
# The enclosure is for the well and the spacing is to the bitcell wells
|
||||
y_offset = self.bitcell_array.height + self.m2_gap
|
||||
self.precharge_array_inst=self.add_inst(name="precharge_array",
|
||||
mod=self.precharge_array,
|
||||
offset=vector(0,y_offset))
|
||||
temp = []
|
||||
for i in range(self.num_cols):
|
||||
temp.append("bl[{0}]".format(i))
|
||||
temp.append("br[{0}]".format(i))
|
||||
temp.extend([self.prefix+"clk_buf_bar", "vdd"])
|
||||
self.connect_inst(temp)
|
||||
|
||||
def add_column_mux_array(self):
|
||||
""" Adding Column Mux when words_per_row > 1 . """
|
||||
if self.col_addr_size > 0:
|
||||
self.column_mux_height = self.column_mux_array.height + self.m2_gap
|
||||
else:
|
||||
self.column_mux_height = 0
|
||||
return
|
||||
|
||||
y_offset = self.column_mux_height
|
||||
self.col_mux_array_inst=self.add_inst(name="column_mux_array",
|
||||
mod=self.column_mux_array,
|
||||
offset=vector(0,y_offset).scale(-1,-1))
|
||||
temp = []
|
||||
for i in range(self.num_cols):
|
||||
temp.append("bl[{0}]".format(i))
|
||||
temp.append("br[{0}]".format(i))
|
||||
for k in range(self.words_per_row):
|
||||
temp.append("sel[{0}]".format(k))
|
||||
for j in range(self.word_size):
|
||||
temp.append("bl_out[{0}]".format(j))
|
||||
temp.append("br_out[{0}]".format(j))
|
||||
temp.append("gnd")
|
||||
self.connect_inst(temp)
|
||||
|
||||
def add_sense_amp_array(self):
|
||||
""" Adding Sense amp """
|
||||
|
||||
y_offset = self.column_mux_height + self.sense_amp_array.height + self.m2_gap
|
||||
self.sense_amp_array_inst=self.add_inst(name="sense_amp_array",
|
||||
mod=self.sense_amp_array,
|
||||
offset=vector(0,y_offset).scale(-1,-1))
|
||||
temp = []
|
||||
for i in range(self.word_size):
|
||||
temp.append("sa_out[{0}]".format(i))
|
||||
if self.words_per_row == 1:
|
||||
temp.append("bl[{0}]".format(i))
|
||||
temp.append("br[{0}]".format(i))
|
||||
else:
|
||||
temp.append("bl_out[{0}]".format(i))
|
||||
temp.append("br_out[{0}]".format(i))
|
||||
|
||||
temp.extend([self.prefix+"s_en", "vdd", "gnd"])
|
||||
self.connect_inst(temp)
|
||||
|
||||
def add_write_driver_array(self):
|
||||
""" Adding Write Driver """
|
||||
|
||||
y_offset = self.sense_amp_array.height + self.column_mux_height \
|
||||
+ self.m2_gap + self.write_driver_array.height
|
||||
self.write_driver_array_inst=self.add_inst(name="write_driver_array",
|
||||
mod=self.write_driver_array,
|
||||
offset=vector(0,y_offset).scale(-1,-1))
|
||||
|
||||
temp = []
|
||||
for i in range(self.word_size):
|
||||
temp.append("BANK_DIN[{0}]".format(i))
|
||||
for i in range(self.word_size):
|
||||
if (self.words_per_row == 1):
|
||||
temp.append("bl[{0}]".format(i))
|
||||
temp.append("br[{0}]".format(i))
|
||||
else:
|
||||
temp.append("bl_out[{0}]".format(i))
|
||||
temp.append("br_out[{0}]".format(i))
|
||||
temp.extend([self.prefix+"w_en", "vdd", "gnd"])
|
||||
self.connect_inst(temp)
|
||||
|
||||
def add_tri_gate_array(self):
|
||||
""" data tri gate to drive the data bus """
|
||||
y_offset = self.sense_amp_array.height+self.column_mux_height \
|
||||
+ self.m2_gap + self.tri_gate_array.height
|
||||
self.tri_gate_array_inst=self.add_inst(name="tri_gate_array",
|
||||
mod=self.tri_gate_array,
|
||||
offset=vector(0,y_offset).scale(-1,-1))
|
||||
|
||||
temp = []
|
||||
for i in range(self.word_size):
|
||||
temp.append("sa_out[{0}]".format(i))
|
||||
for i in range(self.word_size):
|
||||
temp.append("DOUT[{0}]".format(i))
|
||||
temp.extend([self.prefix+"tri_en", self.prefix+"tri_en_bar", "vdd", "gnd"])
|
||||
self.connect_inst(temp)
|
||||
|
||||
def add_row_decoder(self):
|
||||
""" Add the hierarchical row decoder """
|
||||
|
||||
|
||||
# The address and control bus will be in between decoder and the main memory array
|
||||
# This bus will route address bits to the decoder input and column mux inputs.
|
||||
# The wires are actually routed after we placed the stuff on both sides.
|
||||
# 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.
|
||||
|
||||
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=vector(x_offset,0))
|
||||
|
||||
temp = []
|
||||
for i in range(self.row_addr_size):
|
||||
temp.append("A[{0}]".format(i+self.col_addr_size))
|
||||
for j in range(self.num_rows):
|
||||
temp.append("dec_out[{0}]".format(j))
|
||||
temp.extend(["vdd", "gnd"])
|
||||
self.connect_inst(temp)
|
||||
|
||||
def add_wordline_driver(self):
|
||||
""" Wordline Driver """
|
||||
|
||||
# The wordline driver is placed to the right of the main 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))
|
||||
|
||||
temp = []
|
||||
for i in range(self.num_rows):
|
||||
temp.append("dec_out[{0}]".format(i))
|
||||
for i in range(self.num_rows):
|
||||
temp.append("wl[{0}]".format(i))
|
||||
temp.append(self.prefix+"clk_buf")
|
||||
temp.append("vdd")
|
||||
temp.append("gnd")
|
||||
self.connect_inst(temp)
|
||||
|
||||
|
||||
def add_column_decoder_module(self):
|
||||
"""
|
||||
Create a 2:4 or 3:8 column address decoder.
|
||||
"""
|
||||
# Place the col decoder right aligned with row decoder
|
||||
x_off = -(self.central_bus_width + self.wordline_driver.width + self.col_decoder.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))
|
||||
|
||||
temp = []
|
||||
for i in range(self.col_addr_size):
|
||||
temp.append("A[{0}]".format(i))
|
||||
for j in range(self.num_col_addr_lines):
|
||||
temp.append("sel[{0}]".format(j))
|
||||
temp.extend(["vdd", "gnd"])
|
||||
self.connect_inst(temp)
|
||||
|
||||
def add_column_decoder(self):
|
||||
"""
|
||||
Create a decoder to decode column select lines. This could be an inverter/buffer for 1:2,
|
||||
2:4 decoder, or 3:8 decoder.
|
||||
"""
|
||||
if self.col_addr_size == 0:
|
||||
return
|
||||
elif self.col_addr_size == 1:
|
||||
self.col_decoder = pinvbuf(height=self.mod_dff.height)
|
||||
self.add_mod(self.col_decoder)
|
||||
elif self.col_addr_size == 2:
|
||||
self.col_decoder = self.row_decoder.pre2_4
|
||||
elif self.col_addr_size == 3:
|
||||
self.col_decoder = self.row_decoder.pre3_8
|
||||
else:
|
||||
# No error checking before?
|
||||
debug.error("Invalid column decoder?",-1)
|
||||
|
||||
self.add_column_decoder_module()
|
||||
|
||||
|
||||
def add_bank_select(self):
|
||||
""" Instantiate the bank select logic. """
|
||||
|
||||
if not self.num_banks > 1:
|
||||
return
|
||||
|
||||
x_off = -(self.row_decoder.width + self.central_bus_width + self.wordline_driver.width)
|
||||
if self.col_addr_size > 0:
|
||||
y_off = min(self.col_decoder_inst.by(), self.col_mux_array_inst.by())
|
||||
else:
|
||||
y_off = self.row_decoder_inst.by()
|
||||
y_off -= (self.bank_select.height + drc["well_to_well"])
|
||||
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)
|
||||
|
||||
temp = []
|
||||
temp.extend(self.input_control_signals)
|
||||
temp.append("bank_sel")
|
||||
temp.extend(self.control_signals)
|
||||
temp.extend(["vdd", "gnd"])
|
||||
self.connect_inst(temp)
|
||||
|
||||
def route_vdd_gnd(self):
|
||||
""" Propagate all vdd/gnd pins up to this level for all modules """
|
||||
|
||||
# These are the instances that every bank has
|
||||
top_instances = [self.bitcell_array_inst,
|
||||
self.precharge_array_inst,
|
||||
self.sense_amp_array_inst,
|
||||
self.write_driver_array_inst,
|
||||
# self.tri_gate_array_inst,
|
||||
self.row_decoder_inst,
|
||||
self.wordline_driver_inst]
|
||||
# Add these if we use the part...
|
||||
if self.col_addr_size > 0:
|
||||
top_instances.append(self.col_decoder_inst)
|
||||
top_instances.append(self.col_mux_array_inst)
|
||||
|
||||
if self.num_banks > 1:
|
||||
top_instances.append(self.bank_select_inst)
|
||||
|
||||
|
||||
for inst in top_instances:
|
||||
# Column mux has no vdd
|
||||
if self.col_addr_size==0 or (self.col_addr_size>0 and inst != self.col_mux_array_inst):
|
||||
self.copy_layout_pin(inst, "vdd")
|
||||
# Precharge has no gnd
|
||||
if inst != self.precharge_array_inst:
|
||||
self.copy_layout_pin(inst, "gnd")
|
||||
|
||||
def route_bank_select(self):
|
||||
""" Route the bank select logic. """
|
||||
for input_name in self.input_control_signals+["bank_sel"]:
|
||||
self.copy_layout_pin(self.bank_select_inst, input_name)
|
||||
|
||||
for gated_name in self.control_signals:
|
||||
# Connect the inverter output to the central bus
|
||||
out_pos = self.bank_select_inst.get_pin(gated_name).rc()
|
||||
bus_pos = vector(self.bus_xoffset[gated_name], out_pos.y)
|
||||
self.add_path("metal3",[out_pos, bus_pos])
|
||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
offset=bus_pos,
|
||||
rotate=90)
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=out_pos,
|
||||
rotate=90)
|
||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
offset=out_pos,
|
||||
rotate=90)
|
||||
|
||||
|
||||
def setup_layout_constraints(self):
|
||||
""" After the modules are instantiated, find the dimensions for the
|
||||
control bus, power ring, etc. """
|
||||
|
||||
#The minimum point is either the bottom of the address flops,
|
||||
#the column decoder (if there is one) or the tristate output
|
||||
#driver.
|
||||
# Leave room for the output below the tri gate.
|
||||
#tri_gate_min_y_offset = self.tri_gate_array_inst.by() - 3*self.m2_pitch
|
||||
write_driver_min_y_offset = self.write_driver_array_inst.by() - 3*self.m2_pitch
|
||||
row_decoder_min_y_offset = self.row_decoder_inst.by()
|
||||
if self.col_addr_size > 0:
|
||||
col_decoder_min_y_offset = self.col_decoder_inst.by()
|
||||
else:
|
||||
col_decoder_min_y_offset = row_decoder_min_y_offset
|
||||
|
||||
if self.num_banks>1:
|
||||
# The control gating logic is below the decoder
|
||||
# Min of the control gating logic and tri gate.
|
||||
self.min_y_offset = min(col_decoder_min_y_offset - self.bank_select.height, write_driver_min_y_offset)
|
||||
else:
|
||||
# Just the min of the decoder logic logic and tri gate.
|
||||
self.min_y_offset = min(col_decoder_min_y_offset, write_driver_min_y_offset)
|
||||
|
||||
# The max point is always the top of the precharge bitlines
|
||||
# Add a vdd and gnd power rail above the array
|
||||
self.max_y_offset = self.precharge_array_inst.uy() + 3*self.m1_width
|
||||
self.max_x_offset = self.bitcell_array_inst.ur().x + 3*self.m1_width
|
||||
self.min_x_offset = self.row_decoder_inst.lx()
|
||||
|
||||
# # Create the core bbox for the power rings
|
||||
ur = vector(self.max_x_offset, self.max_y_offset)
|
||||
ll = vector(self.min_x_offset, self.min_y_offset)
|
||||
self.core_bbox = [ll, ur]
|
||||
|
||||
self.height = ur.y - ll.y
|
||||
self.width = ur.x - ll.x
|
||||
|
||||
|
||||
|
||||
def route_central_bus(self):
|
||||
""" Create the address, supply, and control signal central bus lines. """
|
||||
|
||||
# Overall central bus width. It includes all the column mux 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_offset = vector(-self.m2_pitch * self.num_control_lines - self.m2_width, 0)
|
||||
control_bus_length = self.bitcell_array_inst.uy()
|
||||
self.bus_xoffset = self.create_vertical_bus(layer="metal2",
|
||||
pitch=self.m2_pitch,
|
||||
offset=control_bus_offset,
|
||||
names=self.control_signals,
|
||||
length=control_bus_length)
|
||||
|
||||
|
||||
|
||||
def route_precharge_to_bitcell_array(self):
|
||||
""" Routing of BL and BR between pre-charge and bitcell array """
|
||||
|
||||
for i in range(self.num_cols):
|
||||
precharge_bl = self.precharge_array_inst.get_pin("bl[{}]".format(i)).bc()
|
||||
precharge_br = self.precharge_array_inst.get_pin("br[{}]".format(i)).bc()
|
||||
bitcell_bl = self.bitcell_array_inst.get_pin("bl[{}]".format(i)).uc()
|
||||
bitcell_br = self.bitcell_array_inst.get_pin("br[{}]".format(i)).uc()
|
||||
|
||||
yoffset = 0.5*(precharge_bl.y+bitcell_bl.y)
|
||||
self.add_path("metal2",[precharge_bl, vector(precharge_bl.x,yoffset),
|
||||
vector(bitcell_bl.x,yoffset), bitcell_bl])
|
||||
self.add_path("metal2",[precharge_br, vector(precharge_br.x,yoffset),
|
||||
vector(bitcell_br.x,yoffset), bitcell_br])
|
||||
|
||||
|
||||
def route_col_mux_to_bitcell_array(self):
|
||||
""" Routing of BL and BR between col mux and bitcell array """
|
||||
|
||||
# Only do this if we have a column mux!
|
||||
if self.col_addr_size==0:
|
||||
return
|
||||
|
||||
for i in range(self.num_cols):
|
||||
col_mux_bl = self.col_mux_array_inst.get_pin("bl[{}]".format(i)).uc()
|
||||
col_mux_br = self.col_mux_array_inst.get_pin("br[{}]".format(i)).uc()
|
||||
bitcell_bl = self.bitcell_array_inst.get_pin("bl[{}]".format(i)).bc()
|
||||
bitcell_br = self.bitcell_array_inst.get_pin("br[{}]".format(i)).bc()
|
||||
|
||||
yoffset = 0.5*(col_mux_bl.y+bitcell_bl.y)
|
||||
self.add_path("metal2",[col_mux_bl, vector(col_mux_bl.x,yoffset),
|
||||
vector(bitcell_bl.x,yoffset), bitcell_bl])
|
||||
self.add_path("metal2",[col_mux_br, vector(col_mux_br.x,yoffset),
|
||||
vector(bitcell_br.x,yoffset), bitcell_br])
|
||||
|
||||
def route_sense_amp_to_col_mux_or_bitcell_array(self):
|
||||
""" Routing of BL and BR between sense_amp and column mux or bitcell array """
|
||||
|
||||
for i in range(self.word_size):
|
||||
sense_amp_bl = self.sense_amp_array_inst.get_pin("bl[{}]".format(i)).uc()
|
||||
sense_amp_br = self.sense_amp_array_inst.get_pin("br[{}]".format(i)).uc()
|
||||
|
||||
if self.col_addr_size>0:
|
||||
# Sense amp is connected to the col mux
|
||||
connect_bl = self.col_mux_array_inst.get_pin("bl_out[{}]".format(i)).bc()
|
||||
connect_br = self.col_mux_array_inst.get_pin("br_out[{}]".format(i)).bc()
|
||||
else:
|
||||
# Sense amp is directly connected to the bitcell array
|
||||
connect_bl = self.bitcell_array_inst.get_pin("bl[{}]".format(i)).bc()
|
||||
connect_br = self.bitcell_array_inst.get_pin("br[{}]".format(i)).bc()
|
||||
|
||||
|
||||
yoffset = 0.5*(sense_amp_bl.y+connect_bl.y)
|
||||
self.add_path("metal2",[sense_amp_bl, vector(sense_amp_bl.x,yoffset),
|
||||
vector(connect_bl.x,yoffset), connect_bl])
|
||||
self.add_path("metal2",[sense_amp_br, vector(sense_amp_br.x,yoffset),
|
||||
vector(connect_br.x,yoffset), connect_br])
|
||||
|
||||
def route_sense_amp_to_trigate(self):
|
||||
""" Routing of sense amp output to tri_gate input """
|
||||
|
||||
for i in range(self.word_size):
|
||||
# Connection of data_out of sense amp to data_in
|
||||
tri_gate_in = self.tri_gate_array_inst.get_pin("in[{}]".format(i)).lc()
|
||||
sa_data_out = self.sense_amp_array_inst.get_pin("data[{}]".format(i)).bc()
|
||||
|
||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
offset=tri_gate_in)
|
||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
offset=sa_data_out)
|
||||
self.add_path("metal3",[sa_data_out,tri_gate_in])
|
||||
|
||||
def route_sense_amp_out(self):
|
||||
""" Add pins for the sense amp output """
|
||||
for i in range(self.word_size):
|
||||
data_pin = self.sense_amp_array_inst.get_pin("data[{}]".format(i))
|
||||
self.add_layout_pin_rect_center(text="DOUT[{}]".format(i),
|
||||
layer=data_pin.layer,
|
||||
offset=data_pin.center(),
|
||||
height=data_pin.height(),
|
||||
width=data_pin.width()),
|
||||
|
||||
def route_tri_gate_out(self):
|
||||
""" Metal 3 routing of tri_gate output data """
|
||||
for i in range(self.word_size):
|
||||
data_pin = self.tri_gate_array_inst.get_pin("out[{}]".format(i))
|
||||
self.add_layout_pin_rect_center(text="DOUT[{}]".format(i),
|
||||
layer=data_pin.layer,
|
||||
offset=data_pin.center(),
|
||||
height=data_pin.height(),
|
||||
width=data_pin.width()),
|
||||
|
||||
|
||||
def route_row_decoder(self):
|
||||
""" Routes the row decoder inputs and supplies """
|
||||
|
||||
# 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)
|
||||
|
||||
|
||||
def route_write_driver(self):
|
||||
""" Connecting write driver """
|
||||
|
||||
for i in range(self.word_size):
|
||||
data_name = "data[{}]".format(i)
|
||||
din_name = "BANK_DIN[{}]".format(i)
|
||||
self.copy_layout_pin(self.write_driver_array_inst, data_name, din_name)
|
||||
|
||||
|
||||
|
||||
def route_wordline_driver(self):
|
||||
""" Connecting Wordline driver output to Bitcell WL connection """
|
||||
|
||||
# we don't care about bends after connecting to the input pin, so let the path code decide.
|
||||
for i in range(self.num_rows):
|
||||
# The pre/post is to access the pin from "outside" the cell to avoid DRCs
|
||||
decoder_out_pos = self.row_decoder_inst.get_pin("decode[{}]".format(i)).rc()
|
||||
driver_in_pos = self.wordline_driver_inst.get_pin("in[{}]".format(i)).lc()
|
||||
mid1 = decoder_out_pos.scale(0.5,1)+driver_in_pos.scale(0.5,0)
|
||||
mid2 = decoder_out_pos.scale(0.5,0)+driver_in_pos.scale(0.5,1)
|
||||
self.add_path("metal1", [decoder_out_pos, mid1, mid2, driver_in_pos])
|
||||
|
||||
# The mid guarantees we exit the input cell to the right.
|
||||
driver_wl_pos = self.wordline_driver_inst.get_pin("wl[{}]".format(i)).rc()
|
||||
bitcell_wl_pos = self.bitcell_array_inst.get_pin("wl[{}]".format(i)).lc()
|
||||
mid1 = driver_wl_pos.scale(0.5,1)+bitcell_wl_pos.scale(0.5,0)
|
||||
mid2 = driver_wl_pos.scale(0.5,0)+bitcell_wl_pos.scale(0.5,1)
|
||||
self.add_path("metal1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos])
|
||||
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
if self.col_addr_size == 1:
|
||||
|
||||
# Connect to sel[0] and sel[1]
|
||||
decode_names = ["Zb", "Z"]
|
||||
|
||||
# The Address LSB
|
||||
self.copy_layout_pin(self.col_decoder_inst, "A", "A[0]")
|
||||
|
||||
elif self.col_addr_size > 1:
|
||||
decode_names = []
|
||||
for i in range(self.num_col_addr_lines):
|
||||
decode_names.append("out[{}]".format(i))
|
||||
|
||||
for i in range(self.col_addr_size):
|
||||
decoder_name = "in[{}]".format(i)
|
||||
addr_name = "A[{}]".format(i)
|
||||
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])
|
||||
self.add_path("metal1",[decode_out_pos, mid1_pos, mid2_pos, mux_addr_pos])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def add_lvs_correspondence_points(self):
|
||||
""" This adds some points for easier debugging if LVS goes wrong.
|
||||
These should probably be turned off by default though, since extraction
|
||||
will show these as ports in the extracted netlist.
|
||||
"""
|
||||
# Add the wordline names
|
||||
for i in range(self.num_rows):
|
||||
wl_name = "wl[{}]".format(i)
|
||||
wl_pin = self.bitcell_array_inst.get_pin(wl_name)
|
||||
self.add_label(text=wl_name,
|
||||
layer="metal1",
|
||||
offset=wl_pin.center())
|
||||
|
||||
# Add the bitline names
|
||||
for i in range(self.num_cols):
|
||||
bl_name = "bl[{}]".format(i)
|
||||
br_name = "br[{}]".format(i)
|
||||
bl_pin = self.bitcell_array_inst.get_pin(bl_name)
|
||||
br_pin = self.bitcell_array_inst.get_pin(br_name)
|
||||
self.add_label(text=bl_name,
|
||||
layer="metal2",
|
||||
offset=bl_pin.center())
|
||||
self.add_label(text=br_name,
|
||||
layer="metal2",
|
||||
offset=br_pin.center())
|
||||
|
||||
# # Add the data output names to the sense amp output
|
||||
# for i in range(self.word_size):
|
||||
# data_name = "data[{}]".format(i)
|
||||
# data_pin = self.sense_amp_array_inst.get_pin(data_name)
|
||||
# self.add_label(text="sa_out[{}]".format(i),
|
||||
# layer="metal2",
|
||||
# offset=data_pin.center())
|
||||
|
||||
# Add labels on the decoder
|
||||
for i in range(self.word_size):
|
||||
data_name = "dec_out[{}]".format(i)
|
||||
pin_name = "in[{}]".format(i)
|
||||
data_pin = self.wordline_driver_inst.get_pin(pin_name)
|
||||
self.add_label(text=data_name,
|
||||
layer="metal1",
|
||||
offset=data_pin.center())
|
||||
|
||||
|
||||
def route_control_lines(self):
|
||||
""" Route the control lines of the entire bank """
|
||||
|
||||
# Make a list of tuples that we will connect.
|
||||
# From control signal to the module pin
|
||||
# Connection from the central bus to the main control block crosses
|
||||
# pre-decoder and this connection is in metal3
|
||||
connection = []
|
||||
#connection.append((self.prefix+"tri_en_bar", self.tri_gate_array_inst.get_pin("en_bar").lc()))
|
||||
#connection.append((self.prefix+"tri_en", self.tri_gate_array_inst.get_pin("en").lc()))
|
||||
connection.append((self.prefix+"clk_buf_bar", self.precharge_array_inst.get_pin("en").lc()))
|
||||
connection.append((self.prefix+"w_en", self.write_driver_array_inst.get_pin("en").lc()))
|
||||
connection.append((self.prefix+"s_en", self.sense_amp_array_inst.get_pin("en").lc()))
|
||||
|
||||
for (control_signal, pin_pos) in connection:
|
||||
control_pos = vector(self.bus_xoffset[control_signal].x ,pin_pos.y)
|
||||
self.add_path("metal1", [control_pos, pin_pos])
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=control_pos,
|
||||
rotate=90)
|
||||
|
||||
# clk to wordline_driver
|
||||
control_signal = self.prefix+"clk_buf"
|
||||
pin_pos = self.wordline_driver_inst.get_pin("en").uc()
|
||||
mid_pos = pin_pos + vector(0,self.m1_pitch)
|
||||
control_x_offset = self.bus_xoffset[control_signal].x
|
||||
control_pos = vector(control_x_offset + self.m1_width, mid_pos.y)
|
||||
self.add_wire(("metal1","via1","metal2"),[pin_pos, mid_pos, control_pos])
|
||||
control_via_pos = vector(control_x_offset, mid_pos.y)
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=control_via_pos,
|
||||
rotate=90)
|
||||
|
||||
def add_control_pins(self):
|
||||
""" Add the control signal input pins """
|
||||
|
||||
for ctrl in self.control_signals:
|
||||
# xoffsets are the center of the rail
|
||||
x_offset = self.bus_xoffset[ctrl].x - 0.5*self.m2_width
|
||||
if self.num_banks > 1:
|
||||
# it's not an input pin if we have multiple banks
|
||||
self.add_label_pin(text=ctrl,
|
||||
layer="metal2",
|
||||
offset=vector(x_offset, self.min_y_offset),
|
||||
width=self.m2_width,
|
||||
height=self.max_y_offset-self.min_y_offset)
|
||||
else:
|
||||
self.add_layout_pin(text=ctrl,
|
||||
layer="metal2",
|
||||
offset=vector(x_offset, self.min_y_offset),
|
||||
width=self.m2_width,
|
||||
height=self.max_y_offset-self.min_y_offset)
|
||||
|
||||
|
||||
def connect_rail_from_right(self,inst, pin, rail):
|
||||
""" Helper routine to connect an unrotated/mirrored oriented instance to the rails """
|
||||
in_pin = inst.get_pin(pin).lc()
|
||||
rail_pos = vector(self.rail_1_x_offsets[rail], in_pin.y)
|
||||
self.add_wire(("metal3","via2","metal2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)])
|
||||
# Bring it up to M2 for M2/M3 routing
|
||||
self.add_via(layers=("metal1","via1","metal2"),
|
||||
offset=in_pin + contact.m1m2.offset,
|
||||
rotate=90)
|
||||
self.add_via(layers=("metal2","via2","metal3"),
|
||||
offset=in_pin + self.m2m3_via_offset,
|
||||
rotate=90)
|
||||
|
||||
|
||||
def connect_rail_from_left(self,inst, pin, rail):
|
||||
""" Helper routine to connect an unrotated/mirrored oriented instance to the rails """
|
||||
in_pin = inst.get_pin(pin).rc()
|
||||
rail_pos = vector(self.rail_1_x_offsets[rail], in_pin.y)
|
||||
self.add_wire(("metal3","via2","metal2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)])
|
||||
self.add_via(layers=("metal1","via1","metal2"),
|
||||
offset=in_pin + contact.m1m2.offset,
|
||||
rotate=90)
|
||||
self.add_via(layers=("metal2","via2","metal3"),
|
||||
offset=in_pin + self.m2m3_via_offset,
|
||||
rotate=90)
|
||||
|
||||
def analytical_delay(self, slew, load):
|
||||
""" return analytical delay of the bank"""
|
||||
decoder_delay = self.row_decoder.analytical_delay(slew, self.wordline_driver.input_load())
|
||||
|
||||
word_driver_delay = self.wordline_driver.analytical_delay(decoder_delay.slew, self.bitcell_array.input_load())
|
||||
|
||||
bitcell_array_delay = self.bitcell_array.analytical_delay(word_driver_delay.slew)
|
||||
|
||||
bl_t_data_out_delay = self.sense_amp_array.analytical_delay(bitcell_array_delay.slew,
|
||||
self.bitcell_array.output_load())
|
||||
# output load of bitcell_array is set to be only small part of bl for sense amp.
|
||||
|
||||
data_t_DATA_delay = self.tri_gate_array.analytical_delay(bl_t_data_out_delay.slew, load)
|
||||
|
||||
result = decoder_delay + word_driver_delay + bitcell_array_delay + bl_t_data_out_delay + data_t_DATA_delay
|
||||
return result
|
||||
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
from itertools import tee
|
||||
import debug
|
||||
from vector3d import vector3d
|
||||
import grid
|
||||
from heapq import heappush,heappop
|
||||
|
||||
class astar_grid(grid.grid):
|
||||
"""
|
||||
Expand the two layer grid to include A* search functions for a source and target.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
""" Create a routing map of width x height cells and 2 in the z-axis. """
|
||||
grid.grid.__init__(self)
|
||||
|
||||
# list of the source/target grid coordinates
|
||||
self.source = []
|
||||
self.target = []
|
||||
|
||||
# priority queue for the maze routing
|
||||
self.q = []
|
||||
|
||||
def set_source(self,n):
|
||||
self.add_map(n)
|
||||
self.map[n].source=True
|
||||
self.source.append(n)
|
||||
|
||||
def set_target(self,n):
|
||||
self.add_map(n)
|
||||
self.map[n].target=True
|
||||
self.target.append(n)
|
||||
|
||||
|
||||
def add_source(self,track_list):
|
||||
debug.info(2,"Adding source list={0}".format(str(track_list)))
|
||||
for n in track_list:
|
||||
if not self.is_blocked(n):
|
||||
debug.info(3,"Adding source ={0}".format(str(n)))
|
||||
self.set_source(n)
|
||||
|
||||
def add_target(self,track_list):
|
||||
debug.info(2,"Adding target list={0}".format(str(track_list)))
|
||||
for n in track_list:
|
||||
if not self.is_blocked(n):
|
||||
self.set_target(n)
|
||||
|
||||
def is_target(self,point):
|
||||
"""
|
||||
Point is in the target set, so we are done.
|
||||
"""
|
||||
return point in self.target
|
||||
|
||||
def reinit(self):
|
||||
""" Reinitialize everything for a new route. """
|
||||
|
||||
# Reset all the cells in the map
|
||||
for p in self.map.values():
|
||||
p.reset()
|
||||
|
||||
# clear source and target pins
|
||||
self.source=[]
|
||||
self.target=[]
|
||||
|
||||
# Clear the queue
|
||||
while len(self.q)>0:
|
||||
heappop(self.q)
|
||||
self.counter = 0
|
||||
|
||||
def init_queue(self):
|
||||
"""
|
||||
Populate the queue with all the source pins with cost
|
||||
to the target. Each item is a path of the grid cells.
|
||||
We will use an A* search, so this cost must be pessimistic.
|
||||
Cost so far will be the length of the path.
|
||||
"""
|
||||
debug.info(4,"Initializing queue.")
|
||||
|
||||
# uniquify the source (and target while we are at it)
|
||||
self.source = list(set(self.source))
|
||||
self.target = list(set(self.target))
|
||||
|
||||
# Counter is used to not require data comparison in Python 3.x
|
||||
# Items will be returned in order they are added during cost ties
|
||||
self.counter = 0
|
||||
for s in self.source:
|
||||
cost = self.cost_to_target(s)
|
||||
debug.info(1,"Init: cost=" + str(cost) + " " + str([s]))
|
||||
heappush(self.q,(cost,self.counter,[s]))
|
||||
self.counter+=1
|
||||
|
||||
|
||||
def astar_route(self,detour_scale):
|
||||
"""
|
||||
This does the A* maze routing with preferred direction routing.
|
||||
"""
|
||||
|
||||
# We set a cost bound of the HPWL for run-time. This can be
|
||||
# over-ridden if the route fails due to pruning a feasible solution.
|
||||
cost_bound = detour_scale*self.cost_to_target(self.source[0])*self.PREFERRED_COST
|
||||
|
||||
# Make sure the queue is empty if we run another route
|
||||
while len(self.q)>0:
|
||||
heappop(self.q)
|
||||
|
||||
# Put the source items into the queue
|
||||
self.init_queue()
|
||||
cheapest_path = None
|
||||
cheapest_cost = None
|
||||
|
||||
# Keep expanding and adding to the priority queue until we are done
|
||||
while len(self.q)>0:
|
||||
# should we keep the path in the queue as well or just the final node?
|
||||
(cost,count,path) = heappop(self.q)
|
||||
debug.info(2,"Queue size: size=" + str(len(self.q)) + " " + str(cost))
|
||||
debug.info(3,"Expanding: cost=" + str(cost) + " " + str(path))
|
||||
|
||||
# expand the last element
|
||||
neighbors = self.expand_dirs(path)
|
||||
debug.info(3,"Neighbors: " + str(neighbors))
|
||||
|
||||
for n in neighbors:
|
||||
# node is added to the map by the expand routine
|
||||
newpath = path + [n]
|
||||
# check if we hit the target and are done
|
||||
if self.is_target(n):
|
||||
return (newpath,self.cost(newpath))
|
||||
elif not self.map[n].visited:
|
||||
# current path cost + predicted cost
|
||||
current_cost = self.cost(newpath)
|
||||
target_cost = self.cost_to_target(n)
|
||||
predicted_cost = current_cost + target_cost
|
||||
# only add the cost if it is less than our bound
|
||||
if (predicted_cost < cost_bound):
|
||||
if (self.map[n].min_cost==-1 or current_cost<self.map[n].min_cost):
|
||||
self.map[n].visited=True
|
||||
self.map[n].min_path = newpath
|
||||
self.map[n].min_cost = predicted_cost
|
||||
debug.info(3,"Enqueuing: cost=" + str(current_cost) + "+" + str(target_cost) + " " + str(newpath))
|
||||
# add the cost to get to this point if we haven't reached it yet
|
||||
heappush(self.q,(predicted_cost,self.counter,newpath))
|
||||
self.counter += 1
|
||||
|
||||
debug.warning("Unable to route path. Expand the detour_scale to allow detours.")
|
||||
return (None,None)
|
||||
|
||||
def expand_dirs(self,path):
|
||||
"""
|
||||
Expand each of the four cardinal directions plus up or down
|
||||
but not expanding to blocked cells. Expands in all directions
|
||||
regardless of preferred directions.
|
||||
"""
|
||||
# expand from the last point
|
||||
point = path[-1]
|
||||
|
||||
neighbors = []
|
||||
|
||||
east = point + vector3d(1,0,0)
|
||||
if not self.is_blocked(east) and not east in path:
|
||||
neighbors.append(east)
|
||||
|
||||
west= point + vector3d(-1,0,0)
|
||||
if not self.is_blocked(west) and not west in path:
|
||||
neighbors.append(west)
|
||||
|
||||
up = point + vector3d(0,0,1)
|
||||
if up.z<2 and not self.is_blocked(up) and not up in path:
|
||||
neighbors.append(up)
|
||||
|
||||
north = point + vector3d(0,1,0)
|
||||
if not self.is_blocked(north) and not north in path:
|
||||
neighbors.append(north)
|
||||
|
||||
south = point + vector3d(0,-1,0)
|
||||
if not self.is_blocked(south) and not south in path:
|
||||
neighbors.append(south)
|
||||
|
||||
down = point + vector3d(0,0,-1)
|
||||
if down.z>=0 and not self.is_blocked(down) and not down in path:
|
||||
neighbors.append(down)
|
||||
|
||||
return neighbors
|
||||
|
||||
|
||||
def hpwl(self, src, dest):
|
||||
"""
|
||||
Return half perimeter wire length from point to another.
|
||||
Either point can have positive or negative coordinates.
|
||||
Include the via penalty if there is one.
|
||||
"""
|
||||
hpwl = max(abs(src.x-dest.x),abs(dest.x-src.x))
|
||||
hpwl += max(abs(src.y-dest.y),abs(dest.y-src.y))
|
||||
hpwl += max(abs(src.z-dest.z),abs(dest.z-src.z))
|
||||
if src.x!=dest.x or src.y!=dest.y:
|
||||
hpwl += self.VIA_COST
|
||||
return hpwl
|
||||
|
||||
def cost_to_target(self,source):
|
||||
"""
|
||||
Find the cheapest HPWL distance to any target point ignoring
|
||||
blockages for A* search.
|
||||
"""
|
||||
cost = self.hpwl(source,self.target[0])
|
||||
for t in self.target:
|
||||
cost = min(self.hpwl(source,t),cost)
|
||||
return cost
|
||||
|
||||
|
||||
def cost(self,path):
|
||||
"""
|
||||
The cost of the path is the length plus a penalty for the number
|
||||
of vias. We assume that non-preferred direction is penalized.
|
||||
"""
|
||||
|
||||
# Ignore the source pin layer change, FIXME?
|
||||
def pairwise(iterable):
|
||||
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
|
||||
a, b = tee(iterable)
|
||||
next(b, None)
|
||||
return zip(a, b)
|
||||
|
||||
|
||||
plist = pairwise(path)
|
||||
cost = 0
|
||||
for p0,p1 in plist:
|
||||
if p0.z != p1.z: # via
|
||||
cost += self.VIA_COST
|
||||
elif p0.x != p1.x: # horizontal
|
||||
cost += self.NONPREFERRED_COST if (p0.z == 1) else self.PREFERRED_COST
|
||||
elif p0.y != p1.y: # vertical
|
||||
cost += self.NONPREFERRED_COST if (p0.z == 0) else self.PREFERRED_COST
|
||||
else:
|
||||
debug.error("Non-changing direction!")
|
||||
|
||||
return cost
|
||||
|
||||
def get_inertia(self,p0,p1):
|
||||
"""
|
||||
Sets the direction based on the previous direction we came from.
|
||||
"""
|
||||
# direction (index) of movement
|
||||
if p0.x==p1.x:
|
||||
return 1
|
||||
elif p0.y==p1.y:
|
||||
return 0
|
||||
else:
|
||||
# z direction
|
||||
return 2
|
||||
|
||||
|
||||
|
||||
|
|
@ -6,16 +6,14 @@ from vector3d import vector3d
|
|||
from cell import cell
|
||||
import os
|
||||
|
||||
from heapq import heappush,heappop
|
||||
|
||||
class grid:
|
||||
"""A two layer routing map. Each cell can be blocked in the vertical
|
||||
"""
|
||||
A two layer routing map. Each cell can be blocked in the vertical
|
||||
or horizontal layer.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
""" Create a routing map of width x height cells and 2 in the z-axis. """
|
||||
""" Initialize the map and define the costs. """
|
||||
|
||||
# costs are relative to a unit grid
|
||||
# non-preferred cost allows an off-direction jog of 1 grid
|
||||
|
|
@ -23,17 +21,10 @@ class grid:
|
|||
self.VIA_COST = 2
|
||||
self.NONPREFERRED_COST = 4
|
||||
self.PREFERRED_COST = 1
|
||||
|
||||
# list of the source/target grid coordinates
|
||||
self.source = []
|
||||
self.target = []
|
||||
|
||||
# let's leave the map sparse, cells are created on demand to reduce memory
|
||||
self.map={}
|
||||
|
||||
# priority queue for the maze routing
|
||||
self.q = []
|
||||
|
||||
def set_blocked(self,n):
|
||||
self.add_map(n)
|
||||
self.map[n].blocked=True
|
||||
|
|
@ -42,30 +33,6 @@ class grid:
|
|||
self.add_map(n)
|
||||
return self.map[n].blocked
|
||||
|
||||
def set_source(self,n):
|
||||
self.add_map(n)
|
||||
self.map[n].source=True
|
||||
self.source.append(n)
|
||||
|
||||
def set_target(self,n):
|
||||
self.add_map(n)
|
||||
self.map[n].target=True
|
||||
self.target.append(n)
|
||||
|
||||
def reinit(self):
|
||||
""" Reinitialize everything for a new route. """
|
||||
|
||||
self.reset_cells()
|
||||
|
||||
# clear source and target pins
|
||||
self.source=[]
|
||||
self.target=[]
|
||||
|
||||
# clear the queue
|
||||
while len(self.q)>0:
|
||||
heappop(self.q)
|
||||
self.counter = 0
|
||||
|
||||
def add_blockage_shape(self,ll,ur,z):
|
||||
debug.info(3,"Adding blockage ll={0} ur={1} z={2}".format(str(ll),str(ur),z))
|
||||
|
||||
|
|
@ -81,134 +48,6 @@ class grid:
|
|||
for n in block_list:
|
||||
self.set_blocked(n)
|
||||
|
||||
def add_source(self,track_list):
|
||||
debug.info(2,"Adding source list={0}".format(str(track_list)))
|
||||
for n in track_list:
|
||||
if not self.is_blocked(n):
|
||||
debug.info(3,"Adding source ={0}".format(str(n)))
|
||||
self.set_source(n)
|
||||
|
||||
def add_target(self,track_list):
|
||||
debug.info(2,"Adding target list={0}".format(str(track_list)))
|
||||
for n in track_list:
|
||||
if not self.is_blocked(n):
|
||||
self.set_target(n)
|
||||
|
||||
def reset_cells(self):
|
||||
"""
|
||||
Reset the path and costs for all the grid cells.
|
||||
"""
|
||||
for p in self.map.values():
|
||||
p.reset()
|
||||
|
||||
|
||||
def add_path(self,path):
|
||||
"""
|
||||
Mark the path in the routing grid for visualization
|
||||
"""
|
||||
self.path=path
|
||||
for p in path:
|
||||
self.map[p].path=True
|
||||
|
||||
def route(self,detour_scale):
|
||||
"""
|
||||
This does the A* maze routing with preferred direction routing.
|
||||
"""
|
||||
|
||||
# We set a cost bound of the HPWL for run-time. This can be
|
||||
# over-ridden if the route fails due to pruning a feasible solution.
|
||||
cost_bound = detour_scale*self.cost_to_target(self.source[0])*self.PREFERRED_COST
|
||||
|
||||
# Make sure the queue is empty if we run another route
|
||||
while len(self.q)>0:
|
||||
heappop(self.q)
|
||||
|
||||
# Put the source items into the queue
|
||||
self.init_queue()
|
||||
cheapest_path = None
|
||||
cheapest_cost = None
|
||||
|
||||
# Keep expanding and adding to the priority queue until we are done
|
||||
while len(self.q)>0:
|
||||
# should we keep the path in the queue as well or just the final node?
|
||||
(cost,count,path) = heappop(self.q)
|
||||
debug.info(2,"Queue size: size=" + str(len(self.q)) + " " + str(cost))
|
||||
debug.info(3,"Expanding: cost=" + str(cost) + " " + str(path))
|
||||
|
||||
# expand the last element
|
||||
neighbors = self.expand_dirs(path)
|
||||
debug.info(3,"Neighbors: " + str(neighbors))
|
||||
|
||||
for n in neighbors:
|
||||
# node is added to the map by the expand routine
|
||||
newpath = path + [n]
|
||||
# check if we hit the target and are done
|
||||
if self.is_target(n):
|
||||
return (newpath,self.cost(newpath))
|
||||
elif not self.map[n].visited:
|
||||
# current path cost + predicted cost
|
||||
current_cost = self.cost(newpath)
|
||||
target_cost = self.cost_to_target(n)
|
||||
predicted_cost = current_cost + target_cost
|
||||
# only add the cost if it is less than our bound
|
||||
if (predicted_cost < cost_bound):
|
||||
if (self.map[n].min_cost==-1 or current_cost<self.map[n].min_cost):
|
||||
self.map[n].visited=True
|
||||
self.map[n].min_path = newpath
|
||||
self.map[n].min_cost = predicted_cost
|
||||
debug.info(3,"Enqueuing: cost=" + str(current_cost) + "+" + str(target_cost) + " " + str(newpath))
|
||||
# add the cost to get to this point if we haven't reached it yet
|
||||
heappush(self.q,(predicted_cost,self.counter,newpath))
|
||||
self.counter += 1
|
||||
|
||||
debug.warning("Unable to route path. Expand the detour_scale to allow detours.")
|
||||
return (None,None)
|
||||
|
||||
def is_target(self,point):
|
||||
"""
|
||||
Point is in the target set, so we are done.
|
||||
"""
|
||||
return point in self.target
|
||||
|
||||
def expand_dirs(self,path):
|
||||
"""
|
||||
Expand each of the four cardinal directions plus up or down
|
||||
but not expanding to blocked cells. Expands in all directions
|
||||
regardless of preferred directions.
|
||||
"""
|
||||
# expand from the last point
|
||||
point = path[-1]
|
||||
|
||||
neighbors = []
|
||||
|
||||
east = point + vector3d(1,0,0)
|
||||
if not self.is_blocked(east) and not east in path:
|
||||
neighbors.append(east)
|
||||
|
||||
west= point + vector3d(-1,0,0)
|
||||
if not self.is_blocked(west) and not west in path:
|
||||
neighbors.append(west)
|
||||
|
||||
up = point + vector3d(0,0,1)
|
||||
if up.z<2 and not self.is_blocked(up) and not up in path:
|
||||
neighbors.append(up)
|
||||
|
||||
north = point + vector3d(0,1,0)
|
||||
if not self.is_blocked(north) and not north in path:
|
||||
neighbors.append(north)
|
||||
|
||||
south = point + vector3d(0,-1,0)
|
||||
if not self.is_blocked(south) and not south in path:
|
||||
neighbors.append(south)
|
||||
|
||||
down = point + vector3d(0,0,-1)
|
||||
if down.z>=0 and not self.is_blocked(down) and not down in path:
|
||||
neighbors.append(down)
|
||||
|
||||
|
||||
|
||||
return neighbors
|
||||
|
||||
def add_map(self,p):
|
||||
"""
|
||||
Add a point to the map if it doesn't exist.
|
||||
|
|
@ -216,52 +55,13 @@ class grid:
|
|||
if p not in self.map.keys():
|
||||
self.map[p]=cell()
|
||||
|
||||
def init_queue(self):
|
||||
"""
|
||||
Populate the queue with all the source pins with cost
|
||||
to the target. Each item is a path of the grid cells.
|
||||
We will use an A* search, so this cost must be pessimistic.
|
||||
Cost so far will be the length of the path.
|
||||
"""
|
||||
debug.info(4,"Initializing queue.")
|
||||
|
||||
# uniquify the source (and target while we are at it)
|
||||
self.source = list(set(self.source))
|
||||
self.target = list(set(self.target))
|
||||
|
||||
# Counter is used to not require data comparison in Python 3.x
|
||||
# Items will be returned in order they are added during cost ties
|
||||
self.counter = 0
|
||||
for s in self.source:
|
||||
cost = self.cost_to_target(s)
|
||||
debug.info(1,"Init: cost=" + str(cost) + " " + str([s]))
|
||||
heappush(self.q,(cost,self.counter,[s]))
|
||||
self.counter+=1
|
||||
|
||||
|
||||
def hpwl(self, src, dest):
|
||||
def add_path(self,path):
|
||||
"""
|
||||
Return half perimeter wire length from point to another.
|
||||
Either point can have positive or negative coordinates.
|
||||
Include the via penalty if there is one.
|
||||
Mark the path in the routing grid for visualization
|
||||
"""
|
||||
hpwl = max(abs(src.x-dest.x),abs(dest.x-src.x))
|
||||
hpwl += max(abs(src.y-dest.y),abs(dest.y-src.y))
|
||||
hpwl += max(abs(src.z-dest.z),abs(dest.z-src.z))
|
||||
if src.x!=dest.x or src.y!=dest.y:
|
||||
hpwl += self.VIA_COST
|
||||
return hpwl
|
||||
|
||||
def cost_to_target(self,source):
|
||||
"""
|
||||
Find the cheapest HPWL distance to any target point ignoring
|
||||
blockages for A* search.
|
||||
"""
|
||||
cost = self.hpwl(source,self.target[0])
|
||||
for t in self.target:
|
||||
cost = min(self.hpwl(source,t),cost)
|
||||
return cost
|
||||
|
||||
self.path=path
|
||||
for p in path:
|
||||
self.map[p].path=True
|
||||
|
||||
def cost(self,path):
|
||||
"""
|
||||
|
|
@ -291,15 +91,6 @@ class grid:
|
|||
|
||||
return cost
|
||||
|
||||
def get_inertia(self,p0,p1):
|
||||
"""
|
||||
Sets the direction based on the previous direction we came from.
|
||||
"""
|
||||
# direction (index) of movement
|
||||
if p0.x==p1.x:
|
||||
return 1
|
||||
elif p0.y==p1.y:
|
||||
return 0
|
||||
else:
|
||||
# z direction
|
||||
return 2
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,15 +3,16 @@ import tech
|
|||
from contact import contact
|
||||
import math
|
||||
import debug
|
||||
import grid
|
||||
from pin_layout import pin_layout
|
||||
from vector import vector
|
||||
from vector3d import vector3d
|
||||
from globals import OPTS
|
||||
|
||||
class router:
|
||||
"""A router class to read an obstruction map from a gds and plan a
|
||||
"""
|
||||
A router class to read an obstruction map from a gds and plan a
|
||||
route on a given layer. This is limited to two layer routes.
|
||||
It populates blockages on a grid class.
|
||||
"""
|
||||
|
||||
def __init__(self, gds_name):
|
||||
|
|
@ -25,8 +26,7 @@ class router:
|
|||
self.reader.loadFromFile(gds_name)
|
||||
self.top_name = self.layout.rootStructureName
|
||||
|
||||
self.source_pins = []
|
||||
self.target_pins = []
|
||||
self.pins = {}
|
||||
# the list of all blockage shapes
|
||||
self.blockages = []
|
||||
# all the paths we've routed so far (to supplement the blockages)
|
||||
|
|
@ -75,17 +75,6 @@ class router:
|
|||
|
||||
|
||||
|
||||
def create_routing_grid(self):
|
||||
"""
|
||||
Create a routing grid that spans given area. Wires cannot exist outside region.
|
||||
"""
|
||||
# We will add a halo around the boundary
|
||||
# of this many tracks
|
||||
size = self.ur - self.ll
|
||||
debug.info(1,"Size: {0} x {1}".format(size.x,size.y))
|
||||
|
||||
self.rg = grid.grid()
|
||||
|
||||
|
||||
def find_pin(self,pin):
|
||||
"""
|
||||
|
|
@ -123,179 +112,12 @@ class router:
|
|||
Convert the routed path to blockages.
|
||||
Keep the other blockages unchanged.
|
||||
"""
|
||||
self.source_pin_name = None
|
||||
self.source_pins = []
|
||||
self.target_pin_name = None
|
||||
self.target_pins = [] # DO NOT clear the blockages as these don't change
|
||||
self.pins = {}
|
||||
# DO NOT clear the blockages as these don't change
|
||||
self.rg.reinit()
|
||||
|
||||
|
||||
def route(self, cell, layers, src, dest, detour_scale=5):
|
||||
"""
|
||||
Route a single source-destination net and return
|
||||
the simplified rectilinear path. Cost factor is how sub-optimal to explore for a feasible route.
|
||||
This is used to speed up the routing when there is not much detouring needed.
|
||||
"""
|
||||
self.cell = cell
|
||||
|
||||
# Clear the pins if we have previously routed
|
||||
if (hasattr(self,'rg')):
|
||||
self.clear_pins()
|
||||
else:
|
||||
# Set up layers and track sizes
|
||||
self.set_layers(layers)
|
||||
# Creat a routing grid over the entire area
|
||||
# FIXME: This could be created only over the routing region,
|
||||
# but this is simplest for now.
|
||||
self.create_routing_grid()
|
||||
# This will get all shapes as blockages
|
||||
self.find_blockages()
|
||||
|
||||
# Get the pin shapes
|
||||
self.get_source(src)
|
||||
self.get_target(dest)
|
||||
|
||||
# Now add the blockages (all shapes except the src/tgt pins)
|
||||
self.add_blockages()
|
||||
# Add blockages from previous paths
|
||||
self.add_path_blockages()
|
||||
|
||||
# Now add the src/tgt if they are not blocked by other shapes
|
||||
self.add_source()
|
||||
self.add_target()
|
||||
|
||||
|
||||
# returns the path in tracks
|
||||
(path,cost) = self.rg.route(detour_scale)
|
||||
if path:
|
||||
debug.info(1,"Found path: cost={0} ".format(cost))
|
||||
debug.info(2,str(path))
|
||||
self.add_route(path)
|
||||
return True
|
||||
else:
|
||||
self.write_debug_gds()
|
||||
# clean up so we can try a reroute
|
||||
self.clear_pins()
|
||||
|
||||
|
||||
return False
|
||||
|
||||
def write_debug_gds(self):
|
||||
"""
|
||||
Write out a GDS file with the routing grid and search information annotated on it.
|
||||
"""
|
||||
# Only add the debug info to the gds file if we have any debugging on.
|
||||
# This is because we may reroute a wire with detours and don't want the debug information.
|
||||
if OPTS.debug_level==0: return
|
||||
|
||||
self.add_router_info()
|
||||
debug.error("Writing debug_route.gds from {0} to {1}".format(self.source_pin_name,self.target_pin_name))
|
||||
self.cell.gds_write("debug_route.gds")
|
||||
|
||||
def add_router_info(self):
|
||||
"""
|
||||
Write the routing grid and router cost, blockage, pins on
|
||||
the boundary layer for debugging purposes. This can only be
|
||||
called once or the labels will overlap.
|
||||
"""
|
||||
debug.info(0,"Adding router info for {0} to {1}".format(self.source_pin_name,self.target_pin_name))
|
||||
grid_keys=self.rg.map.keys()
|
||||
partial_track=vector(0,self.track_width/6.0)
|
||||
for g in grid_keys:
|
||||
shape = self.convert_full_track_to_shape(g)
|
||||
self.cell.add_rect(layer="boundary",
|
||||
offset=shape[0],
|
||||
width=shape[1].x-shape[0].x,
|
||||
height=shape[1].y-shape[0].y)
|
||||
# These are the on grid pins
|
||||
#rect = self.convert_track_to_pin(g)
|
||||
#self.cell.add_rect(layer="boundary",
|
||||
# offset=rect[0],
|
||||
# width=rect[1].x-rect[0].x,
|
||||
# height=rect[1].y-rect[0].y)
|
||||
|
||||
t=self.rg.map[g].get_type()
|
||||
|
||||
# midpoint offset
|
||||
off=vector((shape[1].x+shape[0].x)/2,
|
||||
(shape[1].y+shape[0].y)/2)
|
||||
if g[2]==1:
|
||||
# Upper layer is upper right label
|
||||
type_off=off+partial_track
|
||||
else:
|
||||
# Lower layer is lower left label
|
||||
type_off=off-partial_track
|
||||
if t!=None:
|
||||
self.cell.add_label(text=str(t),
|
||||
layer="text",
|
||||
offset=type_off)
|
||||
self.cell.add_label(text="{0},{1}".format(g[0],g[1]),
|
||||
layer="text",
|
||||
offset=shape[0],
|
||||
zoom=0.05)
|
||||
|
||||
def add_route(self,path):
|
||||
"""
|
||||
Add the current wire route to the given design instance.
|
||||
"""
|
||||
debug.info(3,"Set path: " + str(path))
|
||||
|
||||
# Keep track of path for future blockages
|
||||
self.paths.append(path)
|
||||
|
||||
# This is marked for debug
|
||||
self.rg.add_path(path)
|
||||
|
||||
# For debugging... if the path failed to route.
|
||||
if False or path==None:
|
||||
self.write_debug_gds()
|
||||
|
||||
if 'Xout_4_1' in [self.source_pin_name, self.target_pin_name]:
|
||||
self.write_debug_gds()
|
||||
|
||||
|
||||
# First, simplify the path for
|
||||
#debug.info(1,str(self.path))
|
||||
contracted_path = self.contract_path(path)
|
||||
debug.info(1,str(contracted_path))
|
||||
|
||||
# convert the path back to absolute units from tracks
|
||||
abs_path = map(self.convert_point_to_units,contracted_path)
|
||||
debug.info(1,str(abs_path))
|
||||
self.cell.add_route(self.layers,abs_path)
|
||||
|
||||
|
||||
def add_grid_pin(self,point,add_via=False):
|
||||
"""
|
||||
Create a rectangle at the grid 3D point that is 1/2 DRC smaller
|
||||
than the routing grid on all sides.
|
||||
"""
|
||||
pin = self.convert_track_to_pin(point)
|
||||
self.cell.add_rect(layer=self.layers[2*point.z],
|
||||
offset=pin[0],
|
||||
width=pin[1].x-pin[0].x,
|
||||
height=pin[1].y-pin[0].y)
|
||||
|
||||
if add_via:
|
||||
# offset this by 1/2 the via size
|
||||
c=contact(self.layers, (1, 1))
|
||||
via_offset = vector(-0.5*c.width,-0.5*c.height)
|
||||
self.cell.add_via(self.layers,vector(point[0],point[1])+via_offset)
|
||||
|
||||
|
||||
def create_steiner_routes(self,pins):
|
||||
"""
|
||||
Find a set of steiner points and then return the list of
|
||||
point-to-point routes.
|
||||
"""
|
||||
pass
|
||||
|
||||
def find_steiner_points(self,pins):
|
||||
"""
|
||||
Find the set of steiner points and return them.
|
||||
"""
|
||||
pass
|
||||
|
||||
def translate_coordinates(self, coord, mirr, angle, xyShift):
|
||||
"""
|
||||
Calculate coordinates after flip, rotate, and shift
|
||||
|
|
@ -371,63 +193,12 @@ class router:
|
|||
self.rg.set_blocked(grid)
|
||||
|
||||
|
||||
def get_source(self,pin_name):
|
||||
"""
|
||||
Gets the source pin shapes only. Doesn't add to grid.
|
||||
"""
|
||||
self.source_pin_name = pin_name
|
||||
self.source_pins = self.find_pin(pin_name)
|
||||
|
||||
def add_source(self):
|
||||
"""
|
||||
Mark the grids that are in the pin rectangle ranges to have the source property.
|
||||
pin can be a location or a label.
|
||||
"""
|
||||
|
||||
found_pin = False
|
||||
for pin in self.source_pins:
|
||||
(pin_in_tracks,blockage_in_tracks)=self.convert_pin_to_tracks(pin)
|
||||
if (len(pin_in_tracks)>0):
|
||||
found_pin=True
|
||||
debug.info(1,"Set source: " + str(self.source_pin_name) + " " + str(pin_in_tracks))
|
||||
self.rg.add_source(pin_in_tracks)
|
||||
self.rg.add_blockage(blockage_in_tracks)
|
||||
|
||||
if not found_pin:
|
||||
self.write_debug_gds()
|
||||
debug.check(found_pin,"Unable to find source pin on grid.")
|
||||
|
||||
def get_target(self,pin_name):
|
||||
"""
|
||||
Gets the target pin shapes only. Doesn't add to grid.
|
||||
"""
|
||||
self.target_pin_name = pin_name
|
||||
self.target_pins = self.find_pin(pin_name)
|
||||
|
||||
def add_target(self):
|
||||
"""
|
||||
Mark the grids that are in the pin rectangle ranges to have the target property.
|
||||
pin can be a location or a label.
|
||||
"""
|
||||
found_pin=False
|
||||
for pin in self.target_pins:
|
||||
(pin_in_tracks,blockage_in_tracks)=self.convert_pin_to_tracks(pin)
|
||||
if (len(pin_in_tracks)>0):
|
||||
found_pin=True
|
||||
debug.info(1,"Set target: " + str(self.target_pin_name) + " " + str(pin_in_tracks))
|
||||
self.rg.add_target(pin_in_tracks)
|
||||
self.rg.add_blockage(blockage_in_tracks)
|
||||
|
||||
if not found_pin:
|
||||
self.write_debug_gds()
|
||||
debug.check(found_pin,"Unable to find target pin on grid.")
|
||||
|
||||
def add_blockages(self):
|
||||
""" Add the blockages except the pin shapes """
|
||||
for blockage in self.blockages:
|
||||
is_nonpin_blockage = True
|
||||
# Skip source pin shapes
|
||||
for pin in self.source_pins + self.target_pins:
|
||||
# Skip pin shapes
|
||||
all_pins = [x[0] for x in list(self.pins.values())]
|
||||
for pin in all_pins:
|
||||
if blockage.overlaps(pin):
|
||||
break
|
||||
else:
|
||||
|
|
@ -601,6 +372,35 @@ class router:
|
|||
return [ll,ur]
|
||||
|
||||
|
||||
def get_pin(self,pin_name):
|
||||
"""
|
||||
Gets the pin shapes only. Doesn't add to grid.
|
||||
"""
|
||||
self.pins[pin_name] = self.find_pin(pin_name)
|
||||
|
||||
def add_pin(self,pin_name,is_source=False):
|
||||
"""
|
||||
Mark the grids that are in the pin rectangle ranges to have the pin property.
|
||||
pin can be a location or a label.
|
||||
"""
|
||||
|
||||
found_pin = False
|
||||
for pin in self.pins[pin_name]:
|
||||
(pin_in_tracks,blockage_in_tracks)=self.convert_pin_to_tracks(pin)
|
||||
if (len(pin_in_tracks)>0):
|
||||
found_pin=True
|
||||
if is_source:
|
||||
debug.info(1,"Set source: " + str(pin_name) + " " + str(pin_in_tracks))
|
||||
self.rg.add_source(pin_in_tracks)
|
||||
else:
|
||||
debug.info(1,"Set target: " + str(pin_name) + " " + str(pin_in_tracks))
|
||||
self.rg.add_target(pin_in_tracks)
|
||||
self.rg.add_blockage(blockage_in_tracks)
|
||||
|
||||
if not found_pin:
|
||||
self.write_debug_gds()
|
||||
debug.check(found_pin,"Unable to find pin on grid.")
|
||||
|
||||
# FIXME: This should be replaced with vector.snap_to_grid at some point
|
||||
|
||||
def snap_to_grid(offset):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,216 @@
|
|||
import gdsMill
|
||||
import tech
|
||||
from contact import contact
|
||||
import math
|
||||
import debug
|
||||
from pin_layout import pin_layout
|
||||
from vector import vector
|
||||
from vector3d import vector3d
|
||||
from globals import OPTS
|
||||
from router import router
|
||||
|
||||
class signal_router(router):
|
||||
"""A router class to read an obstruction map from a gds and plan a
|
||||
route on a given layer. This is limited to two layer routes.
|
||||
"""
|
||||
|
||||
def __init__(self, gds_name):
|
||||
"""Use the gds file for the blockages with the top module topName and
|
||||
layers for the layers to route on
|
||||
"""
|
||||
router.__init__(self, gds_name)
|
||||
|
||||
# all the paths we've routed so far (to supplement the blockages)
|
||||
self.paths = []
|
||||
|
||||
|
||||
def create_routing_grid(self):
|
||||
"""
|
||||
Create a routing grid that spans given area. Wires cannot exist outside region.
|
||||
"""
|
||||
# We will add a halo around the boundary
|
||||
# of this many tracks
|
||||
size = self.ur - self.ll
|
||||
debug.info(1,"Size: {0} x {1}".format(size.x,size.y))
|
||||
|
||||
import astar_grid
|
||||
self.rg = astar_grid.astar_grid()
|
||||
|
||||
|
||||
def route(self, cell, layers, src, dest, detour_scale=5):
|
||||
"""
|
||||
Route a single source-destination net and return
|
||||
the simplified rectilinear path. Cost factor is how sub-optimal to explore for a feasible route.
|
||||
This is used to speed up the routing when there is not much detouring needed.
|
||||
"""
|
||||
self.cell = cell
|
||||
|
||||
# Clear the pins if we have previously routed
|
||||
if (hasattr(self,'rg')):
|
||||
self.clear_pins()
|
||||
else:
|
||||
# Set up layers and track sizes
|
||||
self.set_layers(layers)
|
||||
# Creat a routing grid over the entire area
|
||||
# FIXME: This could be created only over the routing region,
|
||||
# but this is simplest for now.
|
||||
self.create_routing_grid()
|
||||
# This will get all shapes as blockages
|
||||
self.find_blockages()
|
||||
|
||||
# Get the pin shapes
|
||||
self.get_pin(src)
|
||||
self.get_pin(dest)
|
||||
|
||||
# Now add the blockages (all shapes except the src/tgt pins)
|
||||
self.add_blockages()
|
||||
# Add blockages from previous paths
|
||||
self.add_path_blockages()
|
||||
|
||||
# Now add the src/tgt if they are not blocked by other shapes
|
||||
self.add_pin(src,True)
|
||||
self.add_pin(dest,False)
|
||||
|
||||
|
||||
# returns the path in tracks
|
||||
(path,cost) = self.rg.astar_route(detour_scale)
|
||||
if path:
|
||||
debug.info(1,"Found path: cost={0} ".format(cost))
|
||||
debug.info(2,str(path))
|
||||
self.add_route(path)
|
||||
return True
|
||||
else:
|
||||
self.write_debug_gds()
|
||||
# clean up so we can try a reroute
|
||||
self.clear_pins()
|
||||
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def add_route(self,path):
|
||||
"""
|
||||
Add the current wire route to the given design instance.
|
||||
"""
|
||||
debug.info(3,"Set path: " + str(path))
|
||||
|
||||
# Keep track of path for future blockages
|
||||
self.paths.append(path)
|
||||
|
||||
# This is marked for debug
|
||||
self.rg.add_path(path)
|
||||
|
||||
# For debugging... if the path failed to route.
|
||||
if False or path==None:
|
||||
self.write_debug_gds()
|
||||
|
||||
|
||||
# First, simplify the path for
|
||||
#debug.info(1,str(self.path))
|
||||
contracted_path = self.contract_path(path)
|
||||
debug.info(1,str(contracted_path))
|
||||
|
||||
# convert the path back to absolute units from tracks
|
||||
abs_path = map(self.convert_point_to_units,contracted_path)
|
||||
debug.info(1,str(abs_path))
|
||||
self.cell.add_route(self.layers,abs_path)
|
||||
|
||||
|
||||
def get_inertia(self,p0,p1):
|
||||
"""
|
||||
Sets the direction based on the previous direction we came from.
|
||||
"""
|
||||
# direction (index) of movement
|
||||
if p0.x!=p1.x:
|
||||
return 0
|
||||
elif p0.y!=p1.y:
|
||||
return 1
|
||||
else:
|
||||
# z direction
|
||||
return 2
|
||||
|
||||
def contract_path(self,path):
|
||||
"""
|
||||
Remove intermediate points in a rectilinear path.
|
||||
"""
|
||||
newpath = [path[0]]
|
||||
for i in range(1,len(path)-1):
|
||||
prev_inertia=self.get_inertia(path[i-1],path[i])
|
||||
next_inertia=self.get_inertia(path[i],path[i+1])
|
||||
# if we switch directions, add the point, otherwise don't
|
||||
if prev_inertia!=next_inertia:
|
||||
newpath.append(path[i])
|
||||
|
||||
# always add the last path
|
||||
newpath.append(path[-1])
|
||||
return newpath
|
||||
|
||||
|
||||
def add_path_blockages(self):
|
||||
"""
|
||||
Go through all of the past paths and add them as blockages.
|
||||
This is so we don't have to write/reload the GDS.
|
||||
"""
|
||||
for path in self.paths:
|
||||
for grid in path:
|
||||
self.rg.set_blocked(grid)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def write_debug_gds(self):
|
||||
"""
|
||||
Write out a GDS file with the routing grid and search information annotated on it.
|
||||
"""
|
||||
# Only add the debug info to the gds file if we have any debugging on.
|
||||
# This is because we may reroute a wire with detours and don't want the debug information.
|
||||
if OPTS.debug_level==0: return
|
||||
|
||||
self.add_router_info()
|
||||
pin_names = list(self.pins.keys())
|
||||
debug.error("Writing debug_route.gds from {0} to {1}".format(self.source_pin_name,self.target_pin_name))
|
||||
self.cell.gds_write("debug_route.gds")
|
||||
|
||||
def add_router_info(self):
|
||||
"""
|
||||
Write the routing grid and router cost, blockage, pins on
|
||||
the boundary layer for debugging purposes. This can only be
|
||||
called once or the labels will overlap.
|
||||
"""
|
||||
debug.info(0,"Adding router info for {0} to {1}".format(self.source_pin_name,self.target_pin_name))
|
||||
grid_keys=self.rg.map.keys()
|
||||
partial_track=vector(0,self.track_width/6.0)
|
||||
for g in grid_keys:
|
||||
shape = self.convert_full_track_to_shape(g)
|
||||
self.cell.add_rect(layer="boundary",
|
||||
offset=shape[0],
|
||||
width=shape[1].x-shape[0].x,
|
||||
height=shape[1].y-shape[0].y)
|
||||
# These are the on grid pins
|
||||
#rect = self.convert_track_to_pin(g)
|
||||
#self.cell.add_rect(layer="boundary",
|
||||
# offset=rect[0],
|
||||
# width=rect[1].x-rect[0].x,
|
||||
# height=rect[1].y-rect[0].y)
|
||||
|
||||
t=self.rg.map[g].get_type()
|
||||
|
||||
# midpoint offset
|
||||
off=vector((shape[1].x+shape[0].x)/2,
|
||||
(shape[1].y+shape[0].y)/2)
|
||||
if g[2]==1:
|
||||
# Upper layer is upper right label
|
||||
type_off=off+partial_track
|
||||
else:
|
||||
# Lower layer is lower left label
|
||||
type_off=off-partial_track
|
||||
if t!=None:
|
||||
self.cell.add_label(text=str(t),
|
||||
layer="text",
|
||||
offset=type_off)
|
||||
self.cell.add_label(text="{0},{1}".format(g[0],g[1]),
|
||||
layer="text",
|
||||
offset=shape[0],
|
||||
zoom=0.05)
|
||||
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
import gdsMill
|
||||
import tech
|
||||
from contact import contact
|
||||
import math
|
||||
import debug
|
||||
import grid
|
||||
from pin_layout import pin_layout
|
||||
from vector import vector
|
||||
from vector3d import vector3d
|
||||
from globals import OPTS
|
||||
from router import router
|
||||
|
||||
class supply_router(router):
|
||||
"""
|
||||
A router class to read an obstruction map from a gds and
|
||||
routes a grid to connect the supply on the two layers.
|
||||
"""
|
||||
|
||||
def __init__(self, gds_name):
|
||||
"""Use the gds file for the blockages with the top module topName and
|
||||
layers for the layers to route on
|
||||
"""
|
||||
|
||||
router.__init__(self, gds_name)
|
||||
|
||||
self.pins = {}
|
||||
|
||||
|
||||
def clear_pins(self):
|
||||
"""
|
||||
Convert the routed path to blockages.
|
||||
Keep the other blockages unchanged.
|
||||
"""
|
||||
self.pins = {}
|
||||
self.rg.reinit()
|
||||
|
||||
|
||||
def route(self, cell, layers, vdd_name="vdd", gnd_name="gnd"):
|
||||
"""
|
||||
Route a single source-destination net and return
|
||||
the simplified rectilinear path.
|
||||
"""
|
||||
self.cell = cell
|
||||
self.pins[vdd_name] = []
|
||||
self.pins[gnd_name] = []
|
||||
|
||||
# Clear the pins if we have previously routed
|
||||
if (hasattr(self,'rg')):
|
||||
self.clear_pins()
|
||||
else:
|
||||
# Set up layers and track sizes
|
||||
self.set_layers(layers)
|
||||
# Creat a routing grid over the entire area
|
||||
# FIXME: This could be created only over the routing region,
|
||||
# but this is simplest for now.
|
||||
self.create_routing_grid()
|
||||
# This will get all shapes as blockages
|
||||
self.find_blockages()
|
||||
|
||||
# Get the pin shapes
|
||||
self.get_pin(vdd_name)
|
||||
self.get_pin(gnd_name)
|
||||
|
||||
# Now add the blockages (all shapes except the src/tgt pins)
|
||||
self.add_blockages()
|
||||
# Add blockages from previous routes
|
||||
self.add_path_blockages()
|
||||
|
||||
# Now add the src/tgt if they are not blocked by other shapes
|
||||
self.add_pin(vdd_name,True)
|
||||
#self.add_pin()
|
||||
|
||||
|
||||
# returns the path in tracks
|
||||
(path,cost) = self.rg.route(detour_scale)
|
||||
if path:
|
||||
debug.info(1,"Found path: cost={0} ".format(cost))
|
||||
debug.info(2,str(path))
|
||||
self.add_route(path)
|
||||
return True
|
||||
else:
|
||||
self.write_debug_gds()
|
||||
# clean up so we can try a reroute
|
||||
self.clear_pins()
|
||||
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def add_route(self,path):
|
||||
"""
|
||||
Add the current wire route to the given design instance.
|
||||
"""
|
||||
debug.info(3,"Set path: " + str(path))
|
||||
|
||||
# Keep track of path for future blockages
|
||||
self.paths.append(path)
|
||||
|
||||
# This is marked for debug
|
||||
self.rg.add_path(path)
|
||||
|
||||
# For debugging... if the path failed to route.
|
||||
if False or path==None:
|
||||
self.write_debug_gds()
|
||||
|
||||
# First, simplify the path for
|
||||
#debug.info(1,str(self.path))
|
||||
contracted_path = self.contract_path(path)
|
||||
debug.info(1,str(contracted_path))
|
||||
|
||||
# convert the path back to absolute units from tracks
|
||||
abs_path = map(self.convert_point_to_units,contracted_path)
|
||||
debug.info(1,str(abs_path))
|
||||
self.cell.add_route(self.layers,abs_path)
|
||||
|
||||
|
||||
|
||||
|
||||
##########################
|
||||
# Gridded supply route functions
|
||||
##########################
|
||||
def create_grid(self, ll, ur):
|
||||
""" Create alternating vdd/gnd lines horizontally """
|
||||
|
||||
self.create_horizontal_grid()
|
||||
self.create_vertical_grid()
|
||||
|
||||
|
||||
def create_horizontal_grid(self):
|
||||
""" Create alternating vdd/gnd lines horizontally """
|
||||
|
||||
pass
|
||||
|
||||
def create_vertical_grid(self):
|
||||
""" Create alternating vdd/gnd lines horizontally """
|
||||
pass
|
||||
|
||||
def route(self):
|
||||
#self.create_grid()
|
||||
pass
|
||||
|
|
@ -20,7 +20,7 @@ class no_blockages_test(openram_test):
|
|||
globals.init_openram("config_{0}".format(OPTS.tech_name))
|
||||
from gds_cell import gds_cell
|
||||
from design import design
|
||||
from router import router
|
||||
from signal_router import signal_router as router
|
||||
|
||||
class routing(design, openram_test):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class blockages_test(openram_test):
|
|||
globals.init_openram("config_{0}".format(OPTS.tech_name))
|
||||
from gds_cell import gds_cell
|
||||
from design import design
|
||||
from router import router
|
||||
from signal_router import signal_router as router
|
||||
|
||||
class routing(design, openram_test):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class same_layer_pins_test(openram_test):
|
|||
globals.init_openram("config_{0}".format(OPTS.tech_name))
|
||||
from gds_cell import gds_cell
|
||||
from design import design
|
||||
from router import router
|
||||
from signal_router import signal_router as router
|
||||
|
||||
class routing(design, openram_test):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class diff_layer_pins_test(openram_test):
|
|||
globals.init_openram("config_{0}".format(OPTS.tech_name))
|
||||
from gds_cell import gds_cell
|
||||
from design import design
|
||||
from router import router
|
||||
from signal_router import signal_router as router
|
||||
|
||||
class routing(design, openram_test):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class two_nets_test(openram_test):
|
|||
globals.init_openram("config_{0}".format(OPTS.tech_name))
|
||||
from gds_cell import gds_cell
|
||||
from design import design
|
||||
from router import router
|
||||
from signal_router import signal_router as router
|
||||
|
||||
class routing(design, openram_test):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class pin_location_test(openram_test):
|
|||
globals.init_openram("config_{0}".format(OPTS.tech_name))
|
||||
from gds_cell import gds_cell
|
||||
from design import design
|
||||
from router import router
|
||||
from signal_router import signal_router as router
|
||||
|
||||
class routing(design, openram_test):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class big_test(openram_test):
|
|||
globals.init_openram("config_{0}".format(OPTS.tech_name))
|
||||
from gds_cell import gds_cell
|
||||
from design import design
|
||||
from router import router
|
||||
from signal_router import signal_router as router
|
||||
|
||||
class routing(design, openram_test):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class expand_region_test(openram_test):
|
|||
globals.init_openram("config_{0}".format(OPTS.tech_name))
|
||||
from gds_cell import gds_cell
|
||||
from design import design
|
||||
from router import router
|
||||
from signal_router import signal_router as router
|
||||
|
||||
class routing(design, openram_test):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
#!/usr/bin/env python3
|
||||
"Run a regresion test the library cells for DRC"
|
||||
|
||||
import unittest
|
||||
from testutils import header,openram_test
|
||||
import sys,os
|
||||
sys.path.append(os.path.join(sys.path[0],"../.."))
|
||||
sys.path.append(os.path.join(sys.path[0],".."))
|
||||
import globals
|
||||
import debug
|
||||
|
||||
OPTS = globals.OPTS
|
||||
|
||||
class no_blockages_test(openram_test):
|
||||
"""
|
||||
Simplest two pin route test with no blockages.
|
||||
"""
|
||||
|
||||
def runTest(self):
|
||||
globals.init_openram("config_{0}".format(OPTS.tech_name))
|
||||
from gds_cell import gds_cell
|
||||
from design import design
|
||||
from supply_router import supply_router as router
|
||||
|
||||
class routing(design, openram_test):
|
||||
"""
|
||||
A generic GDS design that we can route on.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
design.__init__(self, "top")
|
||||
|
||||
# Instantiate a GDS cell with the design
|
||||
gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
|
||||
cell = gds_cell(name, gds_file)
|
||||
self.add_inst(name=name,
|
||||
mod=cell,
|
||||
offset=[0,0])
|
||||
self.connect_inst([])
|
||||
|
||||
r=router(gds_file)
|
||||
layer_stack =("metal3","via1","metal2")
|
||||
self.assertTrue(r.route(self,layer_stack))
|
||||
|
||||
r=routing("10_supply_grid_test_{0}".format(OPTS.tech_name))
|
||||
self.local_drc_check(r)
|
||||
|
||||
# fails if there are any DRC errors on any cells
|
||||
globals.end_openram()
|
||||
|
||||
# instantiate a copy of the class to actually run the test
|
||||
if __name__ == "__main__":
|
||||
(OPTS, args) = globals.parse_args()
|
||||
del sys.argv[1:]
|
||||
header(__file__, OPTS.tech_name)
|
||||
unittest.main()
|
||||
Loading…
Reference in New Issue