diff --git a/compiler/modules/multibank.py b/compiler/modules/multibank.py new file mode 100644 index 00000000..3a63c890 --- /dev/null +++ b/compiler/modules/multibank.py @@ -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 + diff --git a/compiler/router/astar_grid.py b/compiler/router/astar_grid.py new file mode 100644 index 00000000..5715663a --- /dev/null +++ b/compiler/router/astar_grid.py @@ -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=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 + + + diff --git a/compiler/router/grid.py b/compiler/router/grid.py index d57be765..8eb063cf 100644 --- a/compiler/router/grid.py +++ b/compiler/router/grid.py @@ -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=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 + + + diff --git a/compiler/router/router.py b/compiler/router/router.py index 6375decd..b46acfd8 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -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): diff --git a/compiler/router/signal_router.py b/compiler/router/signal_router.py new file mode 100644 index 00000000..7d06262e --- /dev/null +++ b/compiler/router/signal_router.py @@ -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) + diff --git a/compiler/router/supply_router.py b/compiler/router/supply_router.py new file mode 100644 index 00000000..b58e6e16 --- /dev/null +++ b/compiler/router/supply_router.py @@ -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 diff --git a/compiler/router/tests/01_no_blockages_test.py b/compiler/router/tests/01_no_blockages_test.py index 60cc6583..c7344a64 100755 --- a/compiler/router/tests/01_no_blockages_test.py +++ b/compiler/router/tests/01_no_blockages_test.py @@ -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): """ diff --git a/compiler/router/tests/02_blockages_test.py b/compiler/router/tests/02_blockages_test.py index 7d46f02b..2e85b1c2 100755 --- a/compiler/router/tests/02_blockages_test.py +++ b/compiler/router/tests/02_blockages_test.py @@ -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): """ diff --git a/compiler/router/tests/03_same_layer_pins_test.py b/compiler/router/tests/03_same_layer_pins_test.py index 39a72990..98ce3a2a 100755 --- a/compiler/router/tests/03_same_layer_pins_test.py +++ b/compiler/router/tests/03_same_layer_pins_test.py @@ -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): """ diff --git a/compiler/router/tests/04_diff_layer_pins_test.py b/compiler/router/tests/04_diff_layer_pins_test.py index 0390a6b4..cbc21470 100755 --- a/compiler/router/tests/04_diff_layer_pins_test.py +++ b/compiler/router/tests/04_diff_layer_pins_test.py @@ -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): """ diff --git a/compiler/router/tests/05_two_nets_test.py b/compiler/router/tests/05_two_nets_test.py index e19f7d49..166292d0 100755 --- a/compiler/router/tests/05_two_nets_test.py +++ b/compiler/router/tests/05_two_nets_test.py @@ -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): """ diff --git a/compiler/router/tests/06_pin_location_test.py b/compiler/router/tests/06_pin_location_test.py index c157dcb8..e67fed53 100755 --- a/compiler/router/tests/06_pin_location_test.py +++ b/compiler/router/tests/06_pin_location_test.py @@ -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): """ diff --git a/compiler/router/tests/07_big_test.py b/compiler/router/tests/07_big_test.py index e71e0bf4..5f844ec5 100755 --- a/compiler/router/tests/07_big_test.py +++ b/compiler/router/tests/07_big_test.py @@ -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): """ diff --git a/compiler/router/tests/08_expand_region_test.py b/compiler/router/tests/08_expand_region_test.py index 47941b92..96edab57 100755 --- a/compiler/router/tests/08_expand_region_test.py +++ b/compiler/router/tests/08_expand_region_test.py @@ -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): """ diff --git a/compiler/router/tests/10_supply_grid_test.py b/compiler/router/tests/10_supply_grid_test.py new file mode 100755 index 00000000..7d196c6f --- /dev/null +++ b/compiler/router/tests/10_supply_grid_test.py @@ -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()