From cf940fb15d4ee304f69d508861b0e710ab41dc2c Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Wed, 23 Aug 2017 15:02:15 -0700 Subject: [PATCH] Development version of new pin data structure. Tests pass LVS/DRC except for bank level. --- .gitignore | 1 + compiler/bank.py | 1793 +++++++---------- compiler/bitcell.py | 14 +- compiler/bitcell_array.py | 185 +- compiler/calibre.py | 10 +- compiler/contact.py | 42 +- compiler/control_logic.py | 1066 +++++----- compiler/delay_chain.py | 220 ++ compiler/example_config_freepdk45.py | 3 +- compiler/example_config_scn3me_subm.py | 3 +- compiler/gdsMill/gdsMill/vlsiLayout.py | 36 +- compiler/geometry.py | 96 +- compiler/hierarchical_decoder.py | 623 +++--- compiler/hierarchical_predecode.py | 425 ++-- compiler/hierarchical_predecode2x4.py | 113 +- compiler/hierarchical_predecode3x8.py | 120 +- compiler/hierarchy_layout.py | 65 +- compiler/hierarchy_spice.py | 4 +- compiler/logic_effort_dc.py | 188 -- compiler/ms_flop.py | 34 +- compiler/ms_flop_array.py | 177 +- compiler/nand_2.py | 86 +- compiler/nand_3.py | 367 ++-- compiler/nor_2.py | 298 +-- compiler/path.py | 4 +- compiler/pin_layout.py | 137 ++ compiler/pinv.py | 50 +- compiler/precharge.py | 40 +- compiler/precharge_array.py | 68 +- compiler/ptx.py | 8 +- compiler/replica_bitcell.py | 17 +- compiler/replica_bitline.py | 581 +++--- compiler/sense_amp.py | 10 +- compiler/sense_amp_array.py | 117 +- compiler/single_level_column_mux.py | 279 ++- compiler/single_level_column_mux_array.py | 318 ++- compiler/sram.py | 167 +- compiler/tests/03_contact_test.py | 9 +- compiler/tests/03_path_test.py | 10 +- compiler/tests/03_ptx_1finger_nmos_test.py | 5 +- compiler/tests/03_ptx_1finger_pmos_test.py | 5 +- compiler/tests/03_ptx_3finger_nmos_test.py | 5 +- compiler/tests/03_ptx_3finger_pmos_test.py | 5 +- compiler/tests/03_wire_test.py | 13 +- compiler/tests/04_nand_2_test.py | 3 +- compiler/tests/04_nand_3_test.py | 2 +- compiler/tests/04_nor_2_test.py | 3 +- compiler/tests/04_pinv_test.py | 32 +- compiler/tests/04_precharge_test.py | 52 + compiler/tests/04_wordline_driver_test.py | 6 +- compiler/tests/05_bitcell_array_test.py | 7 +- .../tests/06_hierarchical_decoder_test.py | 37 +- .../06_hierarchical_predecode2x4_test.py | 7 +- .../06_hierarchical_predecode3x8_test.py | 7 +- ... 07_single_level_column_mux_array_test.py} | 22 +- compiler/tests/08_precharge_array_test.py | 7 +- compiler/tests/09_sense_amp_array_test.py | 13 +- compiler/tests/10_write_driver_array_test.py | 17 +- compiler/tests/11_ms_flop_array_test.py | 18 +- ...rray_test.py => 12_tri_gate_array_test.py} | 15 +- ...ine_test.py => 13_replica_bitline_test.py} | 4 +- ...fort_dc_test.py => 14_delay_chain_test.py} | 9 +- ...logic_test.py => 16_control_logic_test.py} | 2 +- compiler/tests/19_bank_test.py | 23 +- compiler/tests/20_sram_1bank_test.py | 2 +- compiler/tests/20_sram_2bank_test.py | 2 +- compiler/tests/20_sram_4bank_test.py | 2 +- compiler/tests/config_20_freepdk45.py | 3 +- compiler/tests/config_20_scn3me_subm.py | 3 +- compiler/tri_gate.py | 18 +- compiler/tri_gate_array.py | 170 +- compiler/utils.py | 58 +- compiler/wire.py | 6 +- compiler/wordline_driver.py | 184 +- compiler/write_driver.py | 10 +- compiler/write_driver_array.py | 178 +- technology/freepdk45/gds_lib/cell_6t.gds | Bin 20480 -> 22528 bytes technology/freepdk45/gds_lib/ms_flop.gds | Bin 40960 -> 40960 bytes technology/freepdk45/gds_lib/tri_gate.gds | Bin 12288 -> 12288 bytes technology/freepdk45/tech/tech.py | 2 +- technology/scn3me_subm/gds_lib/cell_6t.gds | Bin 7068 -> 8192 bytes technology/scn3me_subm/gds_lib/ms_flop.gds | Bin 19302 -> 20480 bytes technology/scn3me_subm/gds_lib/tri_gate.gds | Bin 4896 -> 6144 bytes technology/scn3me_subm/tech/tech.py | 2 +- 84 files changed, 4153 insertions(+), 4590 deletions(-) create mode 100644 compiler/delay_chain.py delete mode 100644 compiler/logic_effort_dc.py create mode 100644 compiler/pin_layout.py create mode 100644 compiler/tests/04_precharge_test.py rename compiler/tests/{07_single_level_column_mux_test.py => 07_single_level_column_mux_array_test.py} (65%) rename compiler/tests/{15_tri_gate_array_test.py => 12_tri_gate_array_test.py} (76%) rename compiler/tests/{16_replica_bitline_test.py => 13_replica_bitline_test.py} (95%) rename compiler/tests/{14_logic_effort_dc_test.py => 14_delay_chain_test.py} (87%) rename compiler/tests/{13_control_logic_test.py => 16_control_logic_test.py} (100%) diff --git a/.gitignore b/.gitignore index acb75e8b..bf2aa7b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .DS_Store *~ *.pyc +*.log diff --git a/compiler/bank.py b/compiler/bank.py index 2d0e8e49..cbaefac0 100644 --- a/compiler/bank.py +++ b/compiler/bank.py @@ -21,22 +21,17 @@ class bank(design.design): 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"] + "column_mux_array", "write_driver_array", "tri_gate_array"] for mod_name in mod_list: config_mod_name = getattr(OPTS.config, mod_name) class_file = reload(__import__(config_mod_name)) mod_class = getattr(class_file , config_mod_name) setattr (self, "mod_"+mod_name, mod_class) - self.bitcell_height = self.mod_bitcell.chars["height"] - self.tri_gate_chars = self.mod_tri_gate.chars - if name == "": - self.name = "bank_{0}_{1}".format(word_size, num_words) - else: - self.name = name - design.design.__init__(self, self.name) - debug.info(2, "create sram of size {0} with {1} num of words".format(word_size,num_words)) + 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 @@ -47,9 +42,10 @@ class bank(design.design): self.add_pins() self.create_modules() self.add_modules() + self.setup_layout_constraints() - self.create_layout() + self.route_layout() self.DRC_LVS() def add_pins(self): @@ -59,56 +55,59 @@ class bank(design.design): for i in range(self.addr_size): self.add_pin("ADDR[{0}]".format(i)) + # For more than one bank, we have a bank select and name + # the signals gated_* if(self.num_banks > 1): self.add_pin("bank_select") - self.add_pin("gated_s_en") - self.add_pin("gated_w_en") - self.add_pin("gated_tri_en_bar") - self.add_pin("gated_tri_en") - self.add_pin("gated_clk_bar") - self.add_pin("gated_clk") + prefix = "gated_" else: - self.add_pin("s_en") - self.add_pin("w_en") - self.add_pin("tri_en_bar") - self.add_pin("tri_en") - self.add_pin("clk_bar") - self.add_pin("clk") + prefix = "" + self.add_pin(prefix+"s_en") + self.add_pin(prefix+"w_en") + self.add_pin(prefix+"tri_en_bar") + self.add_pin(prefix+"tri_en") + self.add_pin(prefix+"clk_bar") + self.add_pin(prefix+"clk") self.add_pin("vdd") self.add_pin("gnd") - def create_layout(self): + def route_layout(self): """ Create routing amoung the modules """ self.create_central_bus() - self.route_pre_charge_to_bitcell_array() - self.route_between_sense_amp_and_tri_gate() + self.route_precharge_to_bitcell_array() + self.route_sense_amp_to_trigate() self.route_tri_gate_out() - - self.route_between_wordline_driver_and_bitcell_array() + self.route_wordline_driver() + self.route_row_decoder() self.route_column_address_lines() - self.route_msf_address_to_row_decoder() + self.route_msf_address() self.route_control_lines() - if(self.num_banks > 1): - self.route_bank_select_or2_gates() - self.route_power_rail_vdd() - self.route_power_rail_gnd() + self.add_control_pins() + self.route_vdd_supply() + self.route_gnd_supply() - self.offset_all_coordinates() + #self.offset_all_coordinates() def add_modules(self): - """ Add modules. The order should be maintained.""" + """ Add modules. The order should not matter! """ self.add_bitcell_array() self.add_precharge_array() - self.add_column_mux_array() + + if self.col_addr_size > 0: + self.add_column_mux_array() + if self.col_addr_size > 1: # size 1 is from addr FF + self.add_column_decoder() + self.add_sense_amp_array() self.add_write_driver_array() self.add_msf_data_in() self.add_tri_gate_array() - self.add_hierarchical_decoder() + self.add_row_decoder() self.add_wordline_driver() self.add_msf_address() - self.add_column_line_decoder() - self.add_bank_select_or2_gates() + + if(self.num_banks > 1): + self.add_bank_select() def compute_sizes(self): """ Computes the required sizes to create the bank """ @@ -120,88 +119,84 @@ class bank(design.design): self.col_addr_size = int(log(self.words_per_row, 2)) self.addr_size = self.col_addr_size + self.row_addr_size - assert self.num_rows*self.num_cols, self.word_size*self.num_words - assert self.addr_size, self.col_addr_size + self.row_addr_size - - self.power_rail_width = 10*drc["minwidth_metal1"] + 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 left gnd rail - self.power_rail_width = 10*drc["minwidth_metal2"] - self.left_gnd_rail_gap = 4*drc["minwidth_metal2"] + self.vdd_rail_width = 5*drc["minwidth_metal2"] + self.gnd_rail_width = 5*drc["minwidth_metal2"] # Number of control lines in the bus - self.number_of_control_lines = 6 + self.num_control_lines = 6 + # The order of the control signals on the control bus: + self.control_lines = ["clk", "tri_en_bar", "tri_en", "clk_bar", "w_en", "s_en"] + # The central bus is the column address (both polarities), row address + if self.col_addr_size>0: + self.num_addr_lines = 2**self.col_addr_size + self.row_addr_size + else: + self.num_addr_lines = self.row_addr_size - self.num_central_bus = (2 * self.col_addr_size + self.row_addr_size - + self.number_of_control_lines) + # M1/M2 routing pitch is based on contacted pitch + self.m1m2_via = contact(layer_stack=("metal1", "via1", "metal2")) + self.m2m3_via = contact(layer_stack=("metal2", "via2", "metal3")) + self.m1_pitch = self.m1m2_via.width + max(drc["metal1_to_metal1"],drc["metal2_to_metal2"]) + self.m2_pitch = self.m2m3_via.width + max(drc["metal2_to_metal2"],drc["metal3_to_metal3"]) + self.m2_width = drc["minwidth_metal2"] + self.m3_width = drc["minwidth_metal3"] - # bus gap is choosen 2 times the minimum width to eliminate drc between - # contact on one bus and the adjacent bus - self.width_central_bus = drc["minwidth_metal2"] - self.gap_central_bus = 2*drc["metal2_to_metal2"] - - # Overall central bus gap. It includes all the column mux lines, 6 + # Overall central bus gap. It includes all the column mux lines, # control lines, address flop to decoder lines and a GND power rail in M2 - central_bus_gap = ((self.gap_central_bus + self.width_central_bus) - *(self.num_central_bus + 2)) - self.overall_central_bus_gap = (central_bus_gap + self.power_rail_width - + self.left_gnd_rail_gap) - - self.start_of_right_central_bus = self.gap_central_bus - control_gap = ((self.gap_central_bus + self.width_central_bus) - * self.number_of_control_lines) - self.start_of_left_central_bus = (control_gap + self.power_rail_width - + self.left_gnd_rail_gap - + self.start_of_right_central_bus) + # one pitch on the right on the right of the control lines + self.start_of_right_central_bus = -self.m2_pitch * (self.num_control_lines + 1) + # one pitch on the right on the addr lines and one on the right of the gnd rail + self.start_of_left_central_bus = self.start_of_right_central_bus - self.m2_pitch*(self.num_addr_lines+2) - self.gnd_rail_width + # add a pitch on each end and around the gnd rail + self.overall_central_bus_width = self.m2_pitch * (self.num_control_lines + self.num_addr_lines + 5) + self.gnd_rail_width # Array for control lines - self.control_bus = [] - self.control_signals = ["s_en", "w_en", + self.control_signals = ["s_en", + "w_en", "clk_bar", - "tri_en", "tri_en_bar", + "tri_en", + "tri_en_bar", "clk"] - self.gated_control_signals = ["gated_s_en", "gated_w_en", + self.gated_control_signals = ["gated_s_en", + "gated_w_en", "gated_clk_bar", "gated_tri_en", "gated_tri_en_bar", "gated_clk"] - # Array for bank address positions - self.address_positions = [] - # Array for bank data positions - self.data_positions = [] def create_modules(self): """ Create all the modules using the class loader """ - self.bitcell_array = self.mod_bitcell_array(name="bitcell_array", - cols=self.num_cols, - rows=self.num_rows) + 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(name="precharge_array", - columns=self.num_cols, + self.precharge_array = self.mod_precharge_array(columns=self.num_cols, ptx_width=drc["minwidth_tx"]) self.add_mod(self.precharge_array) if(self.col_addr_size > 0): - self.column_mux_array = self.mod_column_mux_array(rows=self.num_rows, - columns=self.num_cols, + 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.sens_amp_array = self.mod_sense_amp_array(word_size=self.word_size, + 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.sens_amp_array) + 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.decoder = self.mod_decoder(nand2_nmos_width=2*drc["minwidth_tx"], - nand3_nmos_width=3*drc["minwidth_tx"], - rows=self.num_rows) + self.decoder = self.mod_decoder(rows=self.num_rows) self.add_mod(self.decoder) self.msf_address = self.mod_ms_flop_array(name="msf_address", @@ -218,32 +213,18 @@ class bank(design.design): word_size=self.word_size) self.add_mod(self.tri_gate_array) - self.wordline_driver = self.mod_wordline_driver(name="wordline_driver", - rows=self.num_rows) - #self.wordline_driver.logic_effort_sizing(self.num_cols) + self.wordline_driver = self.mod_wordline_driver(rows=self.num_rows) self.add_mod(self.wordline_driver) - self.inv = pinv(nmos_width=drc["minwidth_tx"], - beta=parameter["pinv_beta"], - height=self.bitcell_height) + self.inv = pinv() self.add_mod(self.inv) - # 4x Inverter - self.inv4x = pinv(nmos_width=4*drc["minwidth_tx"], - beta=parameter["pinv_beta"], - height=self.bitcell_height) + # 4x Inverter + self.inv4x = pinv(nmos_width=4*drc["minwidth_tx"]) self.add_mod(self.inv4x) - self.NAND2 = nand_2(nmos_width=2*drc["minwidth_tx"], - height=self.bitcell_height) - self.add_mod(self.NAND2) - - self.NOR2 = nor_2(nmos_width=drc["minwidth_tx"], - height=self.bitcell_height) - self.add_mod(self.NOR2) - - # These aren't for instantiating, but we use them to get the dimensions - self.m1m2_via = contact(layer_stack=("metal1", "via1", "metal2")) + self.nor2 = nor_2() + self.add_mod(self.nor2) # Vertical metal rail gap definition self.metal2_extend_contact = (self.m1m2_via.second_layer_height @@ -256,175 +237,161 @@ class bank(design.design): def add_bitcell_array(self): """ Adding Bitcell Array """ - self.module_offset = vector(0, 0) - self.bitcell_array_position = self.module_offset - self.add_inst(name="bitcell_array", - mod=self.bitcell_array, - offset=self.module_offset) + 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 = temp + ["vdd", "gnd"] + temp.extend(["vdd", "gnd"]) self.connect_inst(temp) def add_precharge_array(self): - """ Adding Pre-charge """ + """ Adding Precharge """ - self.gap_between_precharge_and_bitcell = 5 * drc["minwidth_metal2"] - - y_off = self.bitcell_array.height + self.gap_between_precharge_and_bitcell - self.precharge_array_position = vector(0, y_off) - self.add_inst(name="precharge_array", - mod=self.precharge_array, - offset=self.precharge_array_position) + # The wells must be far enough apart + # We use two well spacings because the bitcells tend to have a shared rail in the height + y_offset = self.bitcell_array.height + 2*drc["pwell_to_nwell"] + 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 = temp + ["clk_bar", "vdd"] + temp.extend(["clk_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.module_offset = vector(0, -self.column_mux_array.height) - self.column_mux_array_position = self.module_offset - self.add_inst(name="column_mux_array", - mod=self.column_mux_array, - offset=self.column_mux_array_position) - 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.word_size): - temp.append("bl_out[{0}]".format( - j*self.words_per_row)) - temp.append("br_out[{0}]".format( - j*self.words_per_row)) + y_offset = self.column_mux_array.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 j in range(self.word_size): + temp.append("bl_out[{0}]".format(j*self.words_per_row)) + temp.append("br_out[{0}]".format(j*self.words_per_row)) + if self.words_per_row == 2: + temp.extend(["A_bar[{}]".format(self.addr_size),"A[{}]".format(self.addr_size)]) + else: for k in range(self.words_per_row): temp.append("sel[{0}]".format(k)) - temp.append("gnd") - self.connect_inst(temp) + temp.append("gnd") + self.connect_inst(temp) def add_sense_amp_array(self): """ Adding Sense amp """ - self.module_offset = vector(0, self.module_offset.y - self.sens_amp_array.height) - self.sens_amp_array_position = self.module_offset - self.add_inst(name="sense_amp_array", - mod=self.sens_amp_array, - offset=self.sens_amp_array_position) + y_offset = self.column_mux_array.height + self.sense_amp_array.height + 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 = [] - if (self.words_per_row == 1): - for j in range(self.word_size): - temp.append("bl[{0}]".format(j*self.words_per_row)) - temp.append("br[{0}]".format(j*self.words_per_row)) - else: - for j in range(self.word_size): - temp.append("bl_out[{0}]".format(j*self.words_per_row)) - temp.append("br_out[{0}]".format(j*self.words_per_row)) - - for i in range(self.word_size): - temp.append("data_out[{0}]".format(i)) - temp = temp + ["s_en", "vdd", "gnd"] + for i in range(0,self.num_cols,self.words_per_row): + temp.append("data_out[{0}]".format(i/self.words_per_row)) + 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(["s_en", "vdd", "gnd"]) self.connect_inst(temp) def add_write_driver_array(self): """ Adding Write Driver """ - self.module_offset = vector(0, self.module_offset.y - self.write_driver_array.height) - self.write_driver_array_position = self.module_offset - self.add_inst(name="write_driver_array", - mod=self.write_driver_array, - offset=self.write_driver_array_position) + y_offset = self.sense_amp_array.height + self.column_mux_array.height + 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("data_in[{0}]".format(i)) - if (self.words_per_row == 1): - for j in range(self.word_size): - temp.append("bl[{0}]".format(j*self.words_per_row)) - temp.append("br[{0}]".format(j*self.words_per_row)) - else: - for j in range(self.word_size): - temp.append("bl_out[{0}]".format(j*self.words_per_row)) - temp.append("br_out[{0}]".format(j*self.words_per_row)) - temp = temp + ["w_en", "vdd", "gnd"] + for i in range(0,self.num_cols,self.words_per_row): + temp.append("data_in[{0}]".format(i/self.words_per_row)) + temp.append("bl[{0}]".format(i)) + temp.append("br[{0}]".format(i)) + temp.extend(["w_en", "vdd", "gnd"]) self.connect_inst(temp) def add_msf_data_in(self): """ data_in flip_flop """ - self.module_offset = vector(0, self.module_offset.y - self.msf_data_in.height) - self.ms_flop_data_in_offset = self.module_offset - self.add_inst(name="data_in_flop_array", - mod=self.msf_data_in, - - offset=self.ms_flop_data_in_offset) + y_offset= self.sense_amp_array.height + self.column_mux_array.height \ + + self.write_driver_array.height + self.msf_data_in.height + self.msf_data_in_inst=self.add_inst(name="data_in_flop_array", + mod=self.msf_data_in, + offset=vector(0,y_offset).scale(-1,-1)) temp = [] for i in range(self.word_size): temp.append("DATA[{0}]".format(i)) - for i in range(self.word_size): temp.append("data_in[{0}]".format(i)) temp.append("data_in_bar[{0}]".format(i)) - temp = temp + ["clk_bar", "vdd", "gnd"] + temp.extend(["clk_bar", "vdd", "gnd"]) self.connect_inst(temp) def add_tri_gate_array(self): """ data tri gate to drive the data bus """ - - self.module_offset = vector(0, self.module_offset.y) - self.tri_gate_array_offset = self.module_offset - self.add_inst(name="trigate_data_array", - mod=self.tri_gate_array, - offset=self.tri_gate_array_offset, - mirror="MX") + y_offset = self.sense_amp_array.height+self.column_mux_array.height \ + + self.write_driver_array.height + self.msf_data_in.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), + mirror="MX") temp = [] for i in range(self.word_size): temp.append("data_out[{0}]".format(i)) for i in range(self.word_size): temp.append("DATA[{0}]".format(i)) - temp = temp + ["tri_en", "tri_en_bar", "vdd", "gnd"] + temp.extend(["tri_en", "tri_en_bar", "vdd", "gnd"]) self.connect_inst(temp) - def add_hierarchical_decoder(self): - """ Hierarchical Decoder """ + def add_row_decoder(self): + """ Add the hierarchical row decoder """ - """ creating space for address bus before we add Decoder. - The bus will be in between decoder and the main Memory array part - This bus will route decoder input and column mux inputs. - For convenient the space is created first so that placement of decoder and address FFs gets easier. - The wires are actually routed after we placed the stuffs on both side""" - self.module_offset = vector(self.decoder.width + self.overall_central_bus_gap, - self.decoder.predecoder_height).scale(-1, -1) - self.decoder_position = self.module_offset - self.add_inst(name="address_decoder", - mod=self.decoder, - offset=self.decoder_position) + # 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. + + decoder_x_offset = self.decoder.width + self.overall_central_bus_width + addr_x_offset = self.msf_address.height + offset = vector(max(decoder_x_offset, addr_x_offset), + self.decoder.predecoder_height) + self.row_decoder_inst=self.add_inst(name="row_decoder", + mod=self.decoder, + offset=offset.scale(-1,-1)) temp = [] for i in range(self.row_addr_size): temp.append("A[{0}]".format(i)) for j in range(self.num_rows): temp.append("decode_out[{0}]".format(j)) - temp = temp + ["vdd", "gnd"] + temp.extend(["vdd", "gnd"]) self.connect_inst(temp) def add_wordline_driver(self): """ Wordline Driver """ - x_off = self.decoder_position.x + self.decoder.row_decoder_width - self.module_offset = vector(x_off, 0) - self.wordline_driver_position = self.module_offset - self.add_inst(name="wordline_driver", - mod=self.wordline_driver, - offset=self.wordline_driver_position) + # The wordline driver is placed to the right of the main decoder width. + # This means that it slightly overlaps with the hierarchical decoder, + # but it shares power rails. This may differ for other decoders later... + x_offset = self.decoder.width + self.overall_central_bus_width - self.decoder.row_decoder_width + self.wordline_driver_inst=self.add_inst(name="wordline_driver", + mod=self.wordline_driver, + offset=vector(x_offset,0).scale(-1,-1)) temp = [] for i in range(self.num_rows): @@ -443,324 +410,289 @@ class bank(design.design): def add_msf_address(self): """ Adding address Flip-flops """ - gap = max(drc["pwell_enclose_nwell"], - 2*drc["minwidth_metal2"]) + # A gap between the hierarchical decoder and addr flops + gap = max(drc["pwell_to_nwell"], 2*self.m2_pitch) - self.module_offset = vector(-self.overall_central_bus_gap - - self.msf_address.height - - 4*drc["minwidth_metal2"], - self.decoder_position.y - gap - - drc["minwidth_metal2"]) - self.msf_address_offset = self.module_offset - self.add_inst(name="address_flop_array", - mod=self.msf_address, - offset=self.msf_address_offset, - mirror="R270") - if(self.col_addr_size == 1): - temp = [] - for i in range(self.row_addr_size): - temp.append("ADDR[{0}]".format(i)) - temp.append("ADDR[{0}]".format(self.row_addr_size)) - - for i in range(self.row_addr_size): - temp.append("A[{0}]".format(i)) - temp.append("A_bar[{0}]".format(i)) - temp.append("sel[1]") - temp.append("sel[0]") - if(self.num_banks > 1): - temp = temp + ["gated_clk", "vdd", "gnd"] - else: - temp = temp + ["clk", "vdd", "gnd"] - self.connect_inst(temp) - else: - temp = [] - for i in range(self.row_addr_size + self.col_addr_size): - temp.append("ADDR[{0}]".format(i)) - for i in range(self.row_addr_size + self.col_addr_size): - temp.append("A[{0}]".format(i)) - temp.append("A_bar[{0}]".format(i)) - if(self.num_banks > 1): - temp = temp + ["gated_clk", "vdd", "gnd"] - else: - temp = temp + ["clk", "vdd", "gnd"] - self.connect_inst(temp) - - # update the min_point - self.min_point = (self.msf_address_offset.y - self.msf_address.width - - 4*drc["minwidth_metal1"]) - - def add_column_line_decoder(self): - """ Create a 2:4 decoder to decode colum select lines if the col_addr_size = 4 """ - - if(self.col_addr_size == 2): - vertical_gap = max(drc["pwell_enclose_nwell"] + drc["minwidth_metal2"], - 3 * drc["minwidth_metal2"] + 3 * drc["metal2_to_metal2"]) - self.col_decoder = self.decoder.pre2_4 - x_off = (self.gap_central_bus + self.width_central_bus - + self.overall_central_bus_gap - + self.col_decoder.width) - y_off =(self.msf_address_offset.y - self.msf_address.width - - self.col_decoder.height - vertical_gap) - self.module_offset = vector(-x_off, y_off) - self.col_decoder_position = self.module_offset - self.add_inst(name="col_address_decoder", - mod=self.decoder.pre2_4, - offset=self.col_decoder_position) - addr_index = self.row_addr_size - temp = [] - for i in range(2): - temp.append("A[{0}]".format(i + self.row_addr_size)) - for j in range(4): - temp.append("sel[{0}]".format(j)) - temp = temp + ["vdd", "gnd"] - self.connect_inst(temp) - - # update the min_point - self.min_point = self.col_decoder_position.y - - def add_bank_select_or2_gates(self): - """ Create an array of and gates to gate the control signals in case - of multiple banks are created in upper level SRAM module """ - + # The address flops go below the hierarchical decoder + decoder_x_offset = self.decoder.width + self.overall_central_bus_width + addr_x_offset = self.msf_address.height + self.overall_central_bus_width + # msf_address is not in the y-coord because it is rotated + offset = vector(max(decoder_x_offset, addr_x_offset), + self.decoder.predecoder_height + gap) + self.msf_address_inst=self.add_inst(name="address_flop_array", + mod=self.msf_address, + offset=offset.scale(-1,-1), + rotate=270) + temp = [] + for i in range(self.row_addr_size+self.col_addr_size): + temp.append("ADDR[{0}]".format(i)) + temp.append("A[{0}]".format(i)) + temp.append("A_bar[{0}]".format(i)) if(self.num_banks > 1): - # update the min_point - self.min_point = (self.min_point - 3*drc["minwidth_metal1"] - - self.number_of_control_lines * self.bitcell_height) - xoffset_nor = (- self.start_of_left_central_bus - self.NOR2.width - - self.inv4x.width) - xoffset_inv = xoffset_nor + self.NOR2.width - self.bank_select_or_position = vector(xoffset_nor, self.min_point) + temp.append("gated_clk") + else: + temp.append("clk") + temp.extend(["vdd", "gnd"]) + self.connect_inst(temp) - # bank select inverter - self.bank_select_inv_position = vector(self.bank_select_or_position.x - - 5 * drc["minwidth_metal2"] - - self.inv4x.width, - self.min_point) - self.add_inst(name="bank_select_inv", - mod=self.inv4x, - offset=self.bank_select_inv_position) - self.connect_inst(["bank_select", "bank_select_bar", "vdd", "gnd"]) - for i in range(self.number_of_control_lines): - # central control bus index - # 5 = clk,4 = tri_en_bar,3 = tri_en,2 = clk_bar,1 = w_en,0 = s_en - name_nor = "bank_selector_nor_{0}".format(i) - name_inv = "bank_selector_inv_{0}".format(i) - nor2_inv_connection_height = (self.inv4x.A_position.y - - self.NOR2.Z_position.y - + 0.5 * drc["minwidth_metal1"]) + def add_column_decoder(self): + """ Create a 2:4 decoder to decode column select lines if the col_addr_size = 4 """ - if (i % 2): - y_offset = self.min_point + self.inv.height*(i + 1) - mod_dir = "MX" - # nor2 output to inv input - y_correct = (self.NOR2.Z_position.y + nor2_inv_connection_height - - 0.5 * drc["minwidth_metal1"]) - else: - y_offset = self.min_point + self.inv.height*i - mod_dir = "R0" - # nor2 output to inv input - y_correct = 0.5 * drc["minwidth_metal1"] - self.NOR2.Z_position.y - connection = vector(xoffset_inv, y_offset - y_correct) + # FIXME: Should just load this rather than reference a level down + if self.col_addr_size == 1: + return # This is done from the FF outputs directly + if self.col_addr_size == 2: + self.col_decoder = self.decoder.pre2_4 + elif self.col_addr_size == 3: + self.col_decoder = self.decoder.pre3_8 + else: + # No error checking before? + debug.error("Invalid column decoder?",-1) + - if i == 3: - self.add_inst(name=name_nor, - mod=self.NOR2, - offset=[xoffset_nor, y_offset], - mirror=mod_dir) - self.connect_inst(["gated_tri_en_bar", - "bank_select_bar", - self.control_signals[i].format(i), - "vdd", - "gnd"]) - # connect the metal1 layer to connect to the old inv output - offset = connection - vector(0, 0.5*drc["minwidth_metal1"]) - self.add_rect(layer="metal1", - offset=offset, - width=self.inv4x.width, - height=drc["minwidth_metal1"]) - elif i == 5: - offset = [xoffset_nor, y_offset - self.NOR2.A_position.y - - 0.5*drc["minwidth_metal1"]] - self.add_rect(layer="metal1", - offset=offset, - width=self.NOR2.width + self.inv4x.width, - height=drc["minwidth_metal1"]) - else: - self.add_inst(name=name_nor, - mod=self.NOR2, - offset=[xoffset_nor, y_offset], - mirror=mod_dir) - self.connect_inst([self.gated_control_signals[i], - "bank_select_bar", - "net_block_nor_inv[{0}]".format(i), - "vdd", - "gnd"]) + # Place the col decoder just to the left of the control bus + x_off = self.m2_pitch + self.overall_central_bus_width + self.col_decoder.width + # Place the col decoder below the the address flops which are below the row decoder (lave some space for wells) + vertical_gap = max(drc["pwell_to_nwell"], 2*self.m2_pitch) + y_off = self.decoder.predecoder_height + self.msf_address.width + self.col_decoder.height + 2*vertical_gap + self.col_decoder_inst=self.add_inst(name="col_address_decoder", + mod=self.col_decoder, + offset=vector(x_off,y_off).scale(-1,-1)) + temp = [] + for i in range(self.col_addr_size): + temp.append("A[{0}]".format(i + self.row_addr_size)) + for j in range(2**self.col_addr_size): + temp.append("sel[{0}]".format(j)) + temp.extend(["vdd", "gnd"]) + self.connect_inst(temp) - self.add_inst(name=name_inv, - mod=self.inv4x, - offset=[xoffset_inv, y_offset], - mirror=mod_dir) - self.connect_inst(["net_block_nor_inv[{0}]".format(i), - self.control_signals[i], - "vdd", - "gnd"]) + def add_bank_select(self): + """Create a bank select signal that is combined with an array of + NOR+INV gates to gate the control signals in case of multiple + banks are created in upper level SRAM module + """ + assert 0 + xoffset_nor = - self.start_of_left_central_bus - self.nor2.width - self.inv4x.width + xoffset_inv = xoffset_nor + self.nor2.width + self.bank_select_or_position = vector(xoffset_nor, self.min_point) - # nor2 output to inv input - for i in range(self.number_of_control_lines -1) : - nor2_inv_connection_height = (self.inv4x.A_position.y - - self.NOR2.Z_position.y - + 0.5 * drc["minwidth_metal1"]) - - if (i % 2): - y_offset = self.min_point + self.inv.height * (i + 1) - mod_dir = "MX" - y_correct = (-self.NOR2.Z_position.y + 0.5 * drc["minwidth_metal1"] - - nor2_inv_connection_height) - else: - y_offset = self.min_point + self.inv.height*i - mod_dir = "R0" - y_correct = self.NOR2.Z_position.y - 0.5 * drc["minwidth_metal1"] + # bank select inverter + self.bank_select_inv_position = vector(self.bank_select_or_position.x + - 5 * drc["minwidth_metal2"] + - self.inv4x.width, + self.min_point) + self.add_inst(name="bank_select_inv", + mod=self.inv4x, + offset=self.bank_select_inv_position) + self.connect_inst(["bank_select", "bank_select_bar", "vdd", "gnd"]) + + for i in range(self.numb_control_lines): + # central control bus index + # 5 = clk,4 = tri_en_bar,3 = tri_en,2 = clk_bar,1 = w_en,0 = s_en + name_nor = "bank_selector_nor_{0}".format(i) + name_inv = "bank_selector_inv_{0}".format(i) + nor2_inv_connection_height = self.inv4x.A_position.y - self.nor2.Z_position.y + 0.5 * drc["minwidth_metal1"] + + if (i % 2): + y_offset = self.min_point + self.inv.height*(i + 1) + mod_dir = "MX" # nor2 output to inv input - connection = vector(xoffset_inv, y_offset + y_correct) + y_correct = self.nor2.Z_position.y + nor2_inv_connection_height - 0.5 * drc["minwidth_metal1"] + else: + y_offset = self.min_point + self.inv.height*i + mod_dir = "R0" + # nor2 output to inv input + y_correct = 0.5 * drc["minwidth_metal1"] - self.nor2.Z_position.y + connection = vector(xoffset_inv, y_offset - y_correct) + + if i == 3: + self.add_inst(name=name_nor, + mod=self.nor2, + offset=[xoffset_nor, y_offset], + mirror=mod_dir) + self.connect_inst(["gated_tri_en_bar", + "bank_select_bar", + self.control_signals[i].format(i), + "vdd", + "gnd"]) + # connect the metal1 layer to connect to the old inv output + offset = connection - vector(0, 0.5*drc["minwidth_metal1"]) self.add_rect(layer="metal1", - offset=connection, - width=drc["minwidth_metal1"], - height=nor2_inv_connection_height) + offset=offset, + width=self.inv4x.width, + height=drc["minwidth_metal1"]) + elif i == 5: + offset = [xoffset_nor, y_offset - self.nor2.A_position.y + - 0.5*drc["minwidth_metal1"]] + self.add_rect(layer="metal1", + offset=offset, + width=self.nor2.width + self.inv4x.width, + height=drc["minwidth_metal1"]) + else: + self.add_inst(name=name_nor, + mod=self.nor2, + offset=[xoffset_nor, y_offset], + mirror=mod_dir) + self.connect_inst([self.gated_control_signals[i], + "bank_select_bar", + "net_block_nor_inv[{0}]".format(i), + "vdd", + "gnd"]) + + self.add_inst(name=name_inv, + mod=self.inv4x, + offset=[xoffset_inv, y_offset], + mirror=mod_dir) + self.connect_inst(["net_block_nor_inv[{0}]".format(i), + self.control_signals[i], + "vdd", + "gnd"]) + + # nor2 output to inv input + for i in range(self.numb_control_lines - 1): + nor2_inv_connection_height = (self.inv4x.A_position.y + - self.nor2.Z_position.y + + 0.5 * drc["minwidth_metal1"]) + + if (i % 2): + y_offset = self.min_point + self.inv.height * (i + 1) + mod_dir = "MX" + y_correct = (-self.nor2.Z_position.y + 0.5 * drc["minwidth_metal1"] + - nor2_inv_connection_height) + else: + y_offset = self.min_point + self.inv.height*i + mod_dir = "R0" + y_correct = self.nor2.Z_position.y - 0.5 * drc["minwidth_metal1"] + # nor2 output to inv input + connection = vector(xoffset_inv, y_offset + y_correct) + self.add_rect(layer="metal1", + offset=connection, + width=drc["minwidth_metal1"], + height=nor2_inv_connection_height) def setup_layout_constraints(self): - """ Calculating layout constraints, width, hwight etc """ + """ Calculating layout constraints, width, height etc """ - tri_gate_min_point = (self.tri_gate_array_offset.y - 6 * drc["minwidth_metal3"] - - self.tri_gate_array.height) + #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_point = self.tri_gate_array_inst.ll().y - 3*self.m2_pitch + addr_min_point = self.msf_address_inst.ll().y - 2*self.m2_pitch + if self.col_addr_size >1: + decoder_min_point = self.col_decoder_inst.ll().y + else: + decoder_min_point = 0 + self.min_point = min(tri_gate_min_point, addr_min_point, decoder_min_point) + if self.num_banks>1: + self.min_point -= self.num_control_lines * self.bitcell.height - self.min_point = min(tri_gate_min_point, self.min_point) - self.max_point = self.precharge_array_position.y + self.precharge_array.height + # The max point is always the top of the precharge bitlines + self.max_point = self.precharge_array_inst.uy() - # VDD constraints - gap_between_bitcell_array_and_vdd = 3 * drc["minwidth_metal1"] - self.right_vdd_x_offset = self.bitcell_array.width + gap_between_bitcell_array_and_vdd - self.right_vdd_position = vector(self.right_vdd_x_offset, self.min_point) + self.height = self.max_point - self.min_point + + # Add an extra gap between the bitcell and the rail + self.right_vdd_x_offset = self.bitcell_array_inst.ur().x + 3 * drc["minwidth_metal1"] + offset = vector(self.right_vdd_x_offset, self.min_point) self.add_layout_pin(text="vdd", layer="metal1", - offset=[self.right_vdd_x_offset, self.min_point], - width=self.power_rail_width, - height=self.max_point - self.min_point) - # the width of the metal rail is 10 times minwidth metal1 and the gap + offset=offset, + width=self.vdd_rail_width, + height=self.height) + # from the edge of the decoder is another 2 times minwidth metal1 - - self.left_vdd_x_offset = (- 14 * drc["minwidth_metal1"] - + min(self.msf_address_offset.x, - self.decoder_position.x)) - self.left_vdd_position = vector(self.left_vdd_x_offset, self.min_point) + self.left_vdd_x_offset = min(self.msf_address_inst.ll().x, self.row_decoder_inst.ll().x) - self.vdd_rail_width - 2*drc["minwidth_metal1"] + offset = vector(self.left_vdd_x_offset, self.min_point) self.add_layout_pin(text="vdd", layer="metal1", - offset=[self.left_vdd_x_offset, self.min_point], - width=self.power_rail_width, - height=self.max_point - self.min_point) + offset=offset, + width=self.vdd_rail_width, + height=self.height) - self.left_gnd_x_offset = (self.left_gnd_rail_gap / 2 - - self.start_of_left_central_bus) - self.left_gnd_position = vector(self.left_gnd_x_offset, self.min_point) + self.gnd_x_offset = self.start_of_right_central_bus - self.gnd_rail_width - self.m2_pitch + offset = vector(self.gnd_x_offset, self.min_point) self.add_layout_pin(text="gnd", layer="metal2", - offset=self.left_gnd_position , - width=self.power_rail_width, - height=self.max_point - self.min_point) + offset=offset, + width=self.gnd_rail_width, + height=self.height) - # Height and Width of the entire bank - self.height = self.max_point - self.min_point - self.width = (self.right_vdd_x_offset - self.left_vdd_x_offset - + self.power_rail_width) + self.width = self.right_vdd_x_offset - self.left_vdd_x_offset + self.vdd_rail_width def create_central_bus(self): - """ Calculating the offset for placing VDD and GND power rails. - Here we determine the lowest point in the layout """ + """ Create the address, supply, and control signal central bus lines. """ + - """ central Control lines central line connection 2*col_addr_size - number of connections for the column mux and row_addr_size number - of connections for the row address""" + # Address lines in central line connection are 2*col_addr_size + # number of connections for the column mux (for both signal and _bar) and row_addr_size (no _bar) - self.central_line_xoffset = [] - msf_to_central_line = (self.row_addr_size * self.msf_address.width - / (self.row_addr_size + self.col_addr_size)) - self.central_line_y_offset = self.msf_address_offset.y - msf_to_central_line - self.central_line_height = self.max_point - self.min_point + self.central_line_xoffset = {} - # Creating the central bus - # Control lines - for i in range(self.number_of_control_lines): - x_offset = (i + 1) * (self.gap_central_bus + self.width_central_bus) - x_offset = -x_offset - self.start_of_right_central_bus - self.central_line_xoffset.append(x_offset) - self.control_bus.append(x_offset) + # Control lines (to the right of the GND rail) + for i in range(self.num_control_lines): + x_offset = self.start_of_right_central_bus + i * self.m2_pitch + self.central_line_xoffset[self.control_lines[i]]=x_offset self.add_rect(layer="metal2", - offset=[x_offset, self.min_point], - width=self.width_central_bus, - height=self.central_line_height) + offset=vector(x_offset, self.min_point), + width=self.m2_width, + height=self.height) - # column mux lines if there is column mux [2 or 4 lines] - for i in range(2 * self.col_addr_size): - x_offset = (i + 1) * (self.gap_central_bus + self.width_central_bus) - x_offset = -x_offset - self.start_of_left_central_bus - self.central_line_xoffset.append(x_offset) - self.add_rect(layer="metal2", - offset=[x_offset, self.central_line_y_offset], - width=self.width_central_bus, - height=-self.central_line_y_offset - 4 * drc["minwidth_metal2"]) - - # row adress lines + # row address lines (to the left of the column mux or GND rail) + # goes from 0 down to the min point for i in range(self.row_addr_size): - x_offset = ((self.gap_central_bus + self.width_central_bus) - * (i + 1 + 2*self.col_addr_size)) - x_offset = - x_offset - self.start_of_left_central_bus - self.central_line_xoffset.append(x_offset) + x_offset = self.start_of_left_central_bus + i * self.m2_pitch + self.central_line_xoffset["A[{}]".format(i)]=x_offset self.add_rect(layer="metal2", - offset=[x_offset, self.central_line_y_offset], - width=self.width_central_bus, - height=-self.central_line_y_offset - 4*drc["minwidth_metal2"]) + offset=vector(x_offset, self.min_point), + width=self.m2_width, + height=-self.min_point) - def route_pre_charge_to_bitcell_array(self): + # column mux lines if there is column mux [2 or 4 lines] (to the left of the GND rail) + # goes from 0 down to the min point + if self.col_addr_size>0: + for i in range(2**self.col_addr_size): + x_offset = self.start_of_left_central_bus + (i + self.row_addr_size) * self.m2_pitch + self.central_line_xoffset["sel[{}]".format(i)]=x_offset + self.add_rect(layer="metal2", + offset=vector(x_offset, self.min_point), + width=self.m2_width, + height=-self.min_point) + + + + + 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): - BL_position = self.precharge_array_position + self.precharge_array.BL_positions[i] - BR_position = self.precharge_array_position + self.precharge_array.BR_positions[i] - correct = vector(0.5*drc["minwidth_metal2"], - self.gap_between_precharge_and_bitcell - -self.precharge_array_position.y) - # these two rectangles cannot be replaced with add_path. They are not connected together. - self.add_rect(layer="metal2", - offset=BL_position.scale(1,0) - correct, - width=drc["minwidth_metal2"], - height=self.gap_between_precharge_and_bitcell) - self.add_rect(layer="metal2", - offset=BR_position.scale(1,0) - correct, - width=drc["minwidth_metal2"], - height=self.gap_between_precharge_and_bitcell) - def route_between_sense_amp_and_tri_gate(self): + 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() + + self.add_path("metal2",[precharge_bl,bitcell_bl]) + self.add_path("metal2",[precharge_br,bitcell_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 of msf_data_out - tri_gate_in_position = (self.tri_gate_array.in_positions[i].scale(1,-1) - + self.tri_gate_array_offset) - sa_data_out_position = (self.sens_amp_array_position - + self.sens_amp_array.Data_out_positions[i]) + tri_gate_in = self.tri_gate_array_inst.get_pin("in[{}]".format(i)).bc() + sa_data_out = self.sense_amp_array_inst.get_pin("data[{}]".format(i)).rc() # rc to get enough overlap - startY = (self.tri_gate_array_offset.y - self.tri_gate_array.height - - 2 * drc["minwidth_metal3"] - + 0.5 * drc["minwidth_metal1"]) - start = vector(tri_gate_in_position.x - 3 * drc["minwidth_metal3"], - startY) + startY = self.tri_gate_array_inst.ll().y - 2*drc["minwidth_metal3"] + 0.5*drc["minwidth_metal1"] + start = vector(tri_gate_in.x - 3 * drc["minwidth_metal3"], startY) m3_min = vector([drc["minwidth_metal3"]] * 2) - mid1 = (tri_gate_in_position.scale(1,0) - + sa_data_out_position.scale(0,1) + m3_min.scale(-3, 1)) - mid2 = sa_data_out_position + m3_min.scale(0.5, 1) + mid1 = tri_gate_in.scale(1,0) + sa_data_out.scale(0,1) + m3_min.scale(-3, 1) + mid2 = sa_data_out + m3_min.scale(0.5, 1) self.add_path("metal3", [start, mid1, mid2]) - mid3 = [tri_gate_in_position.x, startY] - self.add_path("metal2", [start, mid3, tri_gate_in_position]) + mid3 = [tri_gate_in.x, startY] + self.add_path("metal2", [start, mid3, tri_gate_in]) offset = start - vector([0.5*drc["minwidth_metal3"]] * 2) self.add_via(("metal2", "via2", "metal3"),offset) @@ -768,354 +700,307 @@ class bank(design.design): def route_tri_gate_out(self): """ Metal 3 routing of tri_gate output data """ for i in range(self.word_size): - tri_gate_out_position = (self.tri_gate_array.out_positions[i].scale(1,-1) - + self.tri_gate_array_offset) - data_line_position = [tri_gate_out_position.x - 0.5 * drc["minwidth_metal3"], - self.min_point] - # save data line position - self.data_positions.append(data_line_position) + tri_gate_out_position = self.tri_gate_array_inst.get_pin("out[{}]".format(i)).ul() + data_line_position = vector(tri_gate_out_position.x, self.min_point) self.add_via(("metal2", "via2", "metal3"), data_line_position) self.add_rect(layer="metal3", offset=data_line_position, width=drc["minwidth_metal3"], height=tri_gate_out_position.y - self.min_point) + self.add_layout_pin(text="DATA[{}]".format(i), + layer="metal2", + offset=data_line_position) - def route_between_wordline_driver_and_bitcell_array(self): - """ Connecting Wordline driver output to Bitcell WL connection """ - WL_horizontal_distance = (- self.wordline_driver.WL_positions[0].x - - self.wordline_driver_position.x) - via_shift = (self.m1m2_via.second_layer_width - - self.m1m2_via.first_layer_width) / 2 - - for i in range(self.num_rows): - bitcell_WL_position = (self.bitcell_array.WL_positions[i] - + vector(0.5 * drc["minwidth_metal1"], 0)) - worldline_WL_position = (self.wordline_driver.WL_positions[i] - + self.wordline_driver_position) - worldline_decode_out_position = (self.wordline_driver.decode_out_positions[i] - + self.wordline_driver_position) - decoder_decode_out_position = (self.decoder.decode_out_positions[i] - + self.decoder_position) - - WL_vertical_distance = worldline_WL_position.y - bitcell_WL_position.y - decode_out_height = abs(worldline_decode_out_position.y - - decoder_decode_out_position.y) + drc["minwidth_metal1"] - - if(WL_vertical_distance > 0): - y_dir = 1 - else: - y_dir = -1 - WL_vertical_distance += 2 * y_dir * drc["minwidth_metal1"] + def route_row_decoder(self): + """ Routes the row decoder inputs and supplies """ + + for i in range(self.row_addr_size): + # Connect the address rails to the decoder + # Note that the decoder inputs are long vertical rails so spread out the connections verically one per row height + decoder_in_position = self.row_decoder_inst.get_pin("A[{}]".format(i)).ll() + vector(0,(i+1)*self.bitcell.height-2*self.m2_pitch) + rail_position = vector(self.central_line_xoffset["A[{}]".format(i)]+drc["minwidth_metal2"],decoder_in_position.y) self.add_rect(layer="metal1", - offset=decoder_decode_out_position, - width=drc["minwidth_metal1"], - height=y_dir * decode_out_height) + offset=decoder_in_position, + width=rail_position.x-decoder_in_position.x, + height=drc["minwidth_metal1"]) + rail_via = vector(self.central_line_xoffset["A[{}]".format(i)],decoder_in_position.y - drc["minwidth_metal2"]) + self.add_via(layers=("metal1", "via1", "metal2"), + offset=rail_via) + self.add_via(layers=("metal1", "via1", "metal2"), + offset=decoder_in_position) - mid = bitcell_WL_position - vector(WL_horizontal_distance, 0) - target = bitcell_WL_position + vector(- WL_horizontal_distance, - WL_vertical_distance) - self.add_path("metal1", [bitcell_WL_position, mid, target]) + # Route the power and ground, but only BELOW the y=0 since the + # others are connected with the wordline driver. + for gnd_pin in self.row_decoder_inst.get_pins("gnd"): + if gnd_pin.uy()>0: + continue + driver_gnd_position = gnd_pin.rc() + gnd_rail_via = vector(self.gnd_x_offset, driver_gnd_position.y + 0.5*self.m1m2_via.width) + gnd_rail_position = vector(self.gnd_x_offset, driver_gnd_position.y) + self.add_path("metal1", [driver_gnd_position, gnd_rail_position]) + self.add_via(layers=("metal1","via1","metal2"), + offset=gnd_rail_via, + rotate=270) + + # route the vdd rails + for vdd_pin in self.row_decoder_inst.get_pins("vdd"): + if vdd_pin.uy()>0: + continue + y_offset = vdd_pin.rc().y + left_rail_position = vector(self.left_vdd_x_offset, y_offset) + right_rail_position = vector(self.row_decoder_inst.ur().x, y_offset) + self.add_path("metal1", [left_rail_position, right_rail_position]) - # Connecting the vdd of the word line driver to the vdd of the bitcell array. - power_rail_width = (self.bitcell_array.vdd_positions[0].x - - self.wordline_driver.vdd_positions[0].x - - self.wordline_driver_position.x) - for i, offset in enumerate(self.wordline_driver.vdd_positions): - vdd_offset = self.wordline_driver_position + offset - if(i % 2 == 0): - self.add_rect(layer="metal1", - offset=vdd_offset, - width=power_rail_width, - height=drc["minwidth_metal1"]) + + 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 + pre = self.row_decoder_inst.get_pin("decode[{}]".format(i)).lc() + decoder_out_position = self.row_decoder_inst.get_pin("decode[{}]".format(i)).rc() + vector(0.5*drc["minwidth_metal1"],0) + driver_in_position = self.wordline_driver_inst.get_pin("in[{}]".format(i)).lc() + vector(0.5*drc["minwidth_metal1"],0) + post = self.wordline_driver_inst.get_pin("in[{}]".format(i)).rc() + self.add_path("metal1", [pre, decoder_out_position, driver_in_position, post]) + + # The mid guarantees we exit the input cell to the right. + driver_wl_position = self.wordline_driver_inst.get_pin("wl[{}]".format(i)).rc() + mid = driver_wl_position + vector(self.m1_pitch,0) + bitcell_wl_position = self.bitcell_array_inst.get_pin("wl[{}]".format(i)).lc() + self.add_path("metal1", [driver_wl_position, mid, bitcell_wl_position]) + + # route the gnd rails, add contact to rail as well + for gnd_pin in self.wordline_driver_inst.get_pins("gnd"): + driver_gnd_position = gnd_pin.rc() + right_rail_position = vector(self.bitcell_array_inst.ll().x, driver_gnd_position.y) + self.add_path("metal1", [driver_gnd_position, right_rail_position]) + gnd_rail_position = vector(self.gnd_x_offset, driver_gnd_position.y + 0.5*self.m1m2_via.width) + self.add_via(layers=("metal1","via1","metal2"), + offset=gnd_rail_position, + rotate=270) + + # route the vdd rails + for vdd_pin in self.wordline_driver_inst.get_pins("vdd"): + y_offset = vdd_pin.rc().y + left_rail_position = vector(self.left_vdd_x_offset, y_offset) + right_rail_position = vector(self.right_vdd_x_offset+self.vdd_rail_width, y_offset) + self.add_path("metal1", [left_rail_position, right_rail_position]) + + def route_column_address_lines(self): """ Connecting the select lines of column mux to the address bus """ - for i in range(2*self.col_addr_size): - line_index = i + self.number_of_control_lines - col_addr_line_position = (self.column_mux_array.addr_line_positions[i] - + self.column_mux_array_position) - - contact_offset = [self.central_line_xoffset[line_index], - col_addr_line_position.y] - connection_width = (col_addr_line_position.x - - self.central_line_xoffset[line_index]) + if not self.col_addr_size>0: + return + + # Connect the select lines to the column mux + for i in range(2**self.col_addr_size): + name = "sel[{}]".format(i) + col_addr_line_position = self.col_mux_array_inst.get_pin(name).ll() + wire_offset = vector(self.central_line_xoffset[name], col_addr_line_position.y) + contact_offset = vector(self.central_line_xoffset[name], col_addr_line_position.y - drc["minwidth_metal2"]) + connection_width = col_addr_line_position.x - self.central_line_xoffset[name] self.add_via(layers=("metal1", "via1", "metal2"), offset=contact_offset) self.add_rect(layer="metal1", - offset=contact_offset, + offset=wire_offset, width=connection_width, height=drc["minwidth_metal1"]) - + # Take care of the column address decoder routing # If there is a 2:4 decoder for column select lines - if(self.col_addr_size == 2): + # or TODO 3:8 decoder should work too! + if self.col_addr_size > 1: - # The snake connection between last two address flop to the input - # of the 2:4 column_mux line decoder - - for i in range(2): - ff_index = i + self.row_addr_size - current_dout = self.msf_address.dout_positions[ff_index] - msf_row_addr_line_position = (current_dout.rotate_scale(1,-1) - + self.msf_address_offset) - - line_index = self.num_central_bus - 2 + i - line_offset = self.central_line_xoffset[line_index] - y_offset = (self.col_decoder_position.y + self.col_decoder.height - + (i + 1) * drc["metal2_to_metal2"] - + i * drc["minwidth_metal2"]) - - gap = drc["minwidth_metal2"] + 2 * drc["metal2_to_metal2"] - input_rail_x = self.col_decoder_position.x - (i + 1) * gap - A_position = (self.col_decoder_position - + self.col_decoder.A_positions[i]) - offset = [input_rail_x - 0.5 * drc["minwidth_metal2"], - A_position.y] - self.add_rect(layer="metal1", - offset=offset, - width=A_position.x - input_rail_x, - height=drc["minwidth_metal1"]) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=offset) - - source = msf_row_addr_line_position - mid1 = [line_offset, msf_row_addr_line_position.y] - mid2 = [line_offset, y_offset] - mid3 = [input_rail_x, y_offset] - target = [input_rail_x, self.col_decoder_position.y] - self.add_path("metal2", [source, mid1, mid2, mid3, target]) - - # connections between outputs of 2:4 decoder to the extension of + # connections between outputs of decoder to the extension of # main address bus - for i in range(4): - line_index = i + self.number_of_control_lines - x_offset = self.central_line_xoffset[line_index] - y_offset = self.col_decoder_position.y - contact_offset = vector(x_offset,y_offset) + for i in range(2**self.col_addr_size): + name = "sel[{}]".format(i) + x_offset = self.central_line_xoffset[name] + decode_out_position = self.col_decoder_inst.get_pin("out[{}]".format(i)).rc() + selx_position = vector(self.central_line_xoffset[name]+drc["minwidth_metal2"],decode_out_position.y) + self.add_path("metal1",[decode_out_position, selx_position]) - col_decoder_out_position =(self.col_decoder_position - + self.col_decoder.decode_out_positions[i] - + vector(0, 0.5 * drc["minwidth_metal1"])) - connection_width = (x_offset - col_decoder_out_position.x - + 0.5 * drc["minwidth_metal2"]) - mid1 = col_decoder_out_position + vector(connection_width,0) - mid2 = col_decoder_out_position + vector(connection_width, - -self.central_line_y_offset) + # via on end + decode_out_via = self.col_decoder_inst.get_pin("out[{}]".format(i)).br() + selx_via = vector(self.central_line_xoffset[name],decode_out_via.y - drc["minwidth_metal2"]) + self.add_via(layers=("metal1", "via1", "metal2"), + offset=selx_via) - self.add_wire(layers=("metal1", "via1", "metal2"), - coordinates=[col_decoder_out_position,mid1,mid2]) + # route the gnd rails, add contact to rail as well + for gnd_pin in self.col_decoder_inst.get_pins("gnd"): + driver_gnd_position = gnd_pin.rc() + right_rail_position = vector(self.gnd_x_offset, driver_gnd_position.y) + self.add_path("metal1", [driver_gnd_position, right_rail_position]) + gnd_rail_position = vector(self.gnd_x_offset, driver_gnd_position.y + 0.5*self.m1m2_via.width) + self.add_via(layers=("metal1","via1","metal2"), + offset=gnd_rail_position, + rotate=270) + + # route the vdd rails + for vdd_pin in self.col_decoder_inst.get_pins("vdd"): + y_offset = vdd_pin.rc().y + left_rail_position = vector(self.left_vdd_x_offset, y_offset) + right_rail_position = vector(self.gnd_x_offset, y_offset) + self.add_path("metal1", [left_rail_position, right_rail_position]) + + # The connection between last address flops to the input + # of the column_mux line decoder + for i in range(self.col_addr_size): + ff_index = i + self.row_addr_size + dout_position = self.msf_address_inst.get_pin("dout[{}]".format(ff_index)).rc() + in_position = self.col_decoder_inst.get_pin("in[{}]".format(i)).uc() + mid_position = vector(in_position.x,dout_position.y) + self.add_path("metal3",[dout_position, mid_position, in_position]) + + dout_via = self.msf_address_inst.get_pin("dout[{}]".format(ff_index)).br() + in_via = self.col_decoder_inst.get_pin("in[{}]".format(i)).ul() + self.add_via(layers=("metal2", "via2", "metal3"), + offset=dout_via, + rotate=90) + self.add_via(layers=("metal2", "via2", "metal3"), + offset=in_via) + + + + + # if there are only two column select lines we just connect the dout_bar of the last FF # to only select line and dout of that FF to the other select line - elif(self.col_addr_size == 1): - ff_index = self.row_addr_size - base = self.msf_address_offset - vector(0, 0.5 * drc["minwidth_metal3"]) - dout_position = (self.msf_address.dout_positions[ff_index].rotate_scale(1,-1) - + base) - dout_bar_position = (self.msf_address.dout_bar_positions[ff_index].rotate_scale(1,-1) - + base) + elif self.col_addr_size == 1: - y_offset = self.msf_address_offset.y - self.msf_address.width - height = self.central_line_y_offset - y_offset - - for i in range(2): - self.add_rect(layer="metal2", - offset=[self.central_line_xoffset[i + self.number_of_control_lines], - y_offset], - width=self.width_central_bus, - height=height) - - # dout connection to column select 1 - line_offset = self.central_line_xoffset[self.number_of_control_lines + 1] - connection_width = line_offset - dout_position.x + drc["minwidth_metal2"] - self.add_rect(layer="metal3", - offset=dout_position, - width=connection_width, - height=drc["minwidth_metal3"]) - # two m2m3_via contancts on both end - self.add_via(layers=("metal2", "via2", "metal3"), - offset=[line_offset, - dout_position.y + drc["minwidth_metal3"]], - mirror="R270") - self.add_via(layers=("metal2", "via2", "metal3"), - offset=dout_position, - mirror="R90") - # dout_bar connection to column select 0 - line_offset = self.central_line_xoffset[self.number_of_control_lines] - connection_width = line_offset - dout_bar_position.x + drc["minwidth_metal2"] - self.add_rect(layer="metal3", - offset=dout_bar_position, - width=connection_width, - height=drc["minwidth_metal3"]) - # two m2m3_via contancts on both end - self.add_via(layers=("metal2", "via2", "metal3"), - offset=[line_offset, dout_bar_position.y]) - self.add_via(layers=("metal2", "via2", "metal3"), - offset=(dout_bar_position - + vector(drc["minwidth_metal2"], 0)), - mirror="R90") + dout_bar_position = self.msf_address_inst.get_pin("dout_bar[{}]".format(self.row_addr_size)).rc() + sel0_position = vector(self.central_line_xoffset["sel[0]"]+drc["minwidth_metal2"],dout_bar_position.y) + self.add_path("metal1",[dout_bar_position, sel0_position]) - def route_msf_address_to_row_decoder(self): + # two vias on both ends + dout_bar_via = self.msf_address_inst.get_pin("dout_bar[{}]".format(self.row_addr_size)).br() + sel0_via = vector(self.central_line_xoffset["sel[0]"],dout_bar_via.y - drc["minwidth_metal2"]) + self.add_via(layers=("metal1", "via1", "metal2"), + offset=dout_bar_via, + rotate=90) + self.add_via(layers=("metal1", "via1", "metal2"), + offset=sel0_via) + + dout_position = self.msf_address_inst.get_pin("dout[{}]".format(self.row_addr_size)).rc() + sel1_position = vector(self.central_line_xoffset["sel[1]"]+drc["minwidth_metal2"],dout_position.y) + self.add_path("metal1",[dout_position, sel1_position]) + # two vias on both ends + dout_via = self.msf_address_inst.get_pin("dout[{}]".format(self.row_addr_size)).br() + sel1_via = vector(self.central_line_xoffset["sel[1]"],dout_via.y) + self.add_via(layers=("metal1", "via1", "metal2"), + offset=dout_via, + rotate=90) + self.add_via(layers=("metal1", "via1", "metal2"), + offset=sel1_via) + + + def route_msf_address(self): """ Routing the row address lines from the address ms-flop array to the row-decoder """ - for i in range(self.row_addr_size): - decoder_row_addr_line_position = (self.decoder_position - + self.decoder.A_positions[i]) - - line_index = i + 2*self.col_addr_size + self.number_of_control_lines - connection_width = (self.central_line_xoffset[line_index] + drc["minwidth_metal2"] - - decoder_row_addr_line_position.x) - first_contact_offset = [self.central_line_xoffset[line_index], - decoder_row_addr_line_position.y] - - self.add_rect(layer="metal1", - offset=decoder_row_addr_line_position, - width=connection_width, - height=drc["minwidth_metal1"]) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=first_contact_offset) - - # addres translation should take care of the 270 degree CCW rotation - # addres translation should take care of the 270 degree CCW rotation - msf_row_addr_line_position = (self.msf_address.dout_positions[i].rotate_scale(1,-1) - + self.msf_address_offset - - vector(0, 0.5 * drc["minwidth_metal3"])) - connection_width = (self.central_line_xoffset[line_index] + drc["minwidth_metal2"] - - msf_row_addr_line_position.x) - second_contact_offset = [self.central_line_xoffset[line_index], - msf_row_addr_line_position.y] - - self.add_rect(layer="metal3", - offset=msf_row_addr_line_position, - width=connection_width, - height=drc["minwidth_metal3"]) - self.add_via(layers=("metal2", "via2", "metal3"), - offset=second_contact_offset) - self.add_via(layers=("metal2", "via2", "metal3"), - offset=msf_row_addr_line_position, - mirror="R90") + # Create the address input pins for i in range(self.addr_size): - # Route msf address inputs - msf_din_position = (self.msf_address.din_positions[i].rotate_scale(1,-1) - + self.msf_address_offset - - vector(0, 0.5 * drc["minwidth_metal3"])) - address_position = vector(self.left_vdd_x_offset, - msf_din_position.y) - self.address_positions.append(address_position) - self.add_rect(layer="metal3", - offset=address_position, - width=msf_din_position.x - self.left_vdd_x_offset, - height=drc["minwidth_metal3"]) + msf_din_position = self.msf_address_inst.get_pin("din[{}]".format(i)).ll() + address_position = vector(self.left_vdd_x_offset, msf_din_position.y) + self.add_layout_pin(text="ADDR[{}]".format(i), + layer="metal2", + offset=address_position, + width=msf_din_position.x - self.left_vdd_x_offset, + height=drc["minwidth_metal2"]) + for i in range(self.row_addr_size): + + # Connect the ff outputs to the rails + dout_position = self.msf_address_inst.get_pin("dout[{}]".format(i)).rc() + rail_position = vector(self.central_line_xoffset["A[{}]".format(i)]+drc["minwidth_metal2"],dout_position.y) + self.add_path("metal1",[dout_position, rail_position]) + dout_via = self.msf_address_inst.get_pin("dout[{}]".format(i)).br() + self.add_via(layers=("metal1", "via1", "metal2"), + offset=dout_via, + rotate=90) + + rail_via = vector(self.central_line_xoffset["A[{}]".format(i)],dout_position.y - drc["minwidth_metal2"]) + self.add_via(layers=("metal1", "via1", "metal2"), + offset=rail_via) + + # Connect address FF gnd + for gnd_pin in self.msf_address_inst.get_pins("gnd"): + gnd_via = gnd_pin.br() + self.add_via(layers=("metal2", "via2", "metal3"), + offset=gnd_via, + rotate=90) + gnd_offset = gnd_pin.rc() + rail_offset = vector(self.gnd_x_offset,gnd_offset.y) + self.add_path("metal3",[gnd_offset,rail_offset]) + rail_via = vector(self.gnd_x_offset, gnd_offset.y + 0.5*drc["minwidth_metal3"]) + self.add_via(layers=("metal2", "via2", "metal3"), + offset=rail_via, + rotate=270) + + + # Connect address FF vdd + for vdd_pin in self.msf_address_inst.get_pins("vdd"): + vdd_offset = vdd_pin.bc() + mid = vector(vdd_offset.x, vdd_offset.y - self.m1_pitch) + rail_offset = vector(self.left_vdd_x_offset, mid.y) + self.add_path("metal1", [vdd_offset,mid,rail_offset]) + + + def route_control_lines(self): - """ Routing of control lines """ - # 5 = clk, 4 = tri_en_bar, 3 = tri_en, 2 = clk_bar, 1 = w_en, 0 = s_en - - self.clk_position = [self.central_line_xoffset[5], 0] - self.tri_en_bar_position = [self.central_line_xoffset[4], 0] - self.tri_en_position = [self.central_line_xoffset[3], 0] - self.clk_bar_position = [self.central_line_xoffset[2], 0] - self.w_en_position = [self.central_line_xoffset[1], 0] - self.s_en_position = [self.central_line_xoffset[0], 0] - - right_hand_mapping = [2, 4, 3, 2, 1, 0] - - right_side = [] - right_side.append(self.ms_flop_data_in_offset - + self.msf_data_in.clk_positions[0] - - vector(0, 0.5 * drc["minwidth_metal1"])) - right_side.append(self.tri_gate_array_offset - + vector(1,-1).scale(self.tri_gate_chars["en_bar"]) - - vector(0, 0.5 * drc["minwidth_metal1"])) - right_side.append(self.tri_gate_array_offset - + vector(1,-1).scale(self.tri_gate_chars["en"]) - - vector(0, 0.5 * drc["minwidth_metal1"])) - right_side.append(self.precharge_array_position - + self.precharge_array.pclk_position) - right_side.append(self.write_driver_array_position - + self.write_driver_array.wen_positions[0]) - right_side.append(self.sens_amp_array_position - + self.sens_amp_array.SCLK_positions[0]) - - """ Routing control signals through the central bus. - Connection of control signal input to the central bus is in metal1 - Connection from the central bus to the main control block crosses - pre-decoder and this connections are in metal3""" - - control_line_offsets = [] - """ Connecting right hand side [sense amp. write_driver , tri state - gates, ffs] to the central bus""" + """ Rout the control lines of the entire bank """ - for i in range(len(right_side)): - bus_line_index = right_hand_mapping[i] - - x_offset = self.central_line_xoffset[bus_line_index] - y_offset = self.tri_gate_array_offset.y - height = self.central_line_y_offset - y_offset - right_side_connection_width = right_side[i].x - self.central_line_xoffset[bus_line_index] - right_side_contact_offset = [self.central_line_xoffset[bus_line_index], - right_side[i].y] + # 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(("clk", self.msf_data_in_inst.get_pin("clk").ll())) + connection.append(("tri_en_bar", self.tri_gate_array_inst.get_pin("en_bar").ll())) + connection.append(("tri_en", self.tri_gate_array_inst.get_pin("en").ll())) + connection.append(("clk", self.precharge_array_inst.get_pin("clk").ll())) + connection.append(("w_en", self.write_driver_array_inst.get_pin("wen").ll())) + connection.append(("s_en", self.sense_amp_array_inst.get_pin("sclk").ll())) + + for (control_signal, pin_position) in connection: + control_x_offset = self.central_line_xoffset[control_signal] + control_position = vector(control_x_offset, pin_position.y) + connection_width = pin_position.x - control_x_offset + via_offset = vector(control_x_offset, pin_position.y) self.add_rect(layer="metal1", - offset=right_side_contact_offset, - width=right_side_connection_width, + offset=control_position, + width=connection_width, height=drc["minwidth_metal1"]) self.add_via(layers=("metal1", "via1", "metal2"), - offset=right_side_contact_offset) + offset=via_offset) + self.add_via(layers=("metal1", "via1", "metal2"), + offset=via_offset) - if(right_side[i].y > 0): - self.add_rect(layer="metal2", - offset=[self.central_line_xoffset[bus_line_index], 0], - width=drc["minwidth_metal2"], - height=right_side[i].y + 2*drc["minwidth_metal2"]) - - """ CLK connection from central bus to MSF address - should we move this somewhere else hard to find when modify""" - msf_address_clk_position = (self.msf_address_offset - + self.msf_address.clk_positions[0].rotate_scale(1,-1) - + vector(- 0.5 * drc["minwidth_metal1"], - 2 * drc["minwidth_metal2"])) - clk_connection_position = (self.msf_address_offset - + vector(self.msf_address.clk_positions[0].y, - 2 * drc["minwidth_metal3"])) - - connection_width = self.central_line_xoffset[5] - clk_connection_position.x + # clk to msf address + control_signal = "clk" + pin_position = self.msf_address_inst.get_pin("clk").uc() + mid_position = pin_position + vector(0,self.m1_pitch) + control_x_offset = self.central_line_xoffset[control_signal] + control_position = vector(control_x_offset + drc["minwidth_metal1"], mid_position.y) + self.add_path("metal1",[pin_position, mid_position, control_position]) + control_via_position = vector(control_x_offset, mid_position.y-0.5*drc["minwidth_metal2"]) self.add_via(layers=("metal1", "via1", "metal2"), - offset=msf_address_clk_position, - mirror="R90") - self.add_via(layers=("metal2", "via2", "metal3"), - offset=msf_address_clk_position, - mirror="R90") - - mid_base = vector(msf_address_clk_position.x, clk_connection_position.y) - mid1 = mid_base + vector(0, 0.5 * drc["minwidth_metal3"]) - mid2 = (mid_base + vector([0.5 * drc["minwidth_metal3"]] * 2) - + vector(connection_width, 0)) - self.add_path(layer="metal3", - coordinates=[msf_address_clk_position,mid1,mid2], - width=drc["minwidth_metal3"]) - - self.add_via(layers=("metal2", "via2", "metal3"), - offset=[self.central_line_xoffset[5], - clk_connection_position.y]) - - # Clk connection from central Bus to wordline_driver - wl_clk_position = self.wordline_driver_position \ - + self.wordline_driver.clk_positions[0] \ - + vector(0.5 * drc["minwidth_metal1"], 0) - - connection_width = (self.central_line_xoffset[5] - wl_clk_position.x - + drc["minwidth_metal1"]) - y_off = self.max_point - 2.5 * drc["minwidth_metal1"] - mid1 = [wl_clk_position.x, y_off] - mid2 = mid1 + vector(connection_width, 0) - self.add_path(layer="metal1", - coordinates=[wl_clk_position, mid1, mid2], - width=drc["minwidth_metal1"]) + offset=control_via_position) + # clk to wordline_driver + control_signal = "clk" + pin_position = self.wordline_driver_inst.get_pin("en").uc() + mid_position = pin_position + vector(0,self.m1_pitch) + control_x_offset = self.central_line_xoffset[control_signal] + control_position = vector(control_x_offset + drc["minwidth_metal1"], mid_position.y) + self.add_wire(("metal1","via1","metal2"),[pin_position, mid_position, control_position]) + control_via_position = vector(control_x_offset, mid_position.y-0.5*drc["minwidth_metal2"]) + self.add_via(layers=("metal1", "via1", "metal2"), + offset=control_via_position) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=[self.central_line_xoffset[5], - self.max_point - 3*drc["minwidth_metal1"]]) + def route_bank_select_or2_gates(self): """ Route array of or gates to gate the control signals in case @@ -1126,12 +1011,12 @@ class bank(design.design): offset=[bank_select_line_xoffset, self.bank_select_or_position.y], width=drc["minwidth_metal2"], - height=self.number_of_control_lines*self.inv.height) + height=self.num_control_lines*self.inv.height) # bank select inverter routing # output side start = self.bank_select_inv_position + self.inv4x.Z_position - end = self.bank_select_or_position + self.NOR2.B_position + end = self.bank_select_or_position + self.nor2.B_position mid = vector(start.x, end.y) self.add_path("metal1", [start, mid, end]) @@ -1146,22 +1031,22 @@ class bank(design.design): self.add_via(layers=("metal2", "via2", "metal3"), offset=self.bank_select_position) - x_offset = (self.bank_select_or_position.x + self.NOR2.width + x_offset = (self.bank_select_or_position.x + self.nor2.width + self.inv4x.width - drc["minwidth_metal1"]) - for i in range(self.number_of_control_lines): + for i in range(self.num_control_lines): base = self.bank_select_or_position.y + self.inv.height * i if(i % 2): Z_y_offset = (base + self.inv.height - self.inv4x.Z_position.y - drc["minwidth_metal1"]) - B_y_offset = (base + self.inv.height - self.NOR2.B_position.y + B_y_offset = (base + self.inv.height - self.nor2.B_position.y - 0.5 * drc["minwidth_metal1"]) - A_y_offset = (base + self.inv.height - self.NOR2.A_position.y + A_y_offset = (base + self.inv.height - self.nor2.A_position.y - 0.5 * drc["minwidth_metal1"]) else: Z_y_offset = (base + self.inv4x.Z_position.y) - B_y_offset = (base + self.NOR2.B_position.y + B_y_offset = (base + self.nor2.B_position.y - 0.5 * drc["minwidth_metal1"]) - A_y_offset = (base + self.NOR2.A_position.y + A_y_offset = (base + self.nor2.A_position.y + 0.5 * drc["minwidth_metal1"] - self.m1m2_via.width) @@ -1198,12 +1083,12 @@ class bank(design.design): offset=[self.bank_select_or_position.x + drc["minwidth_metal1"], A_y_offset], - mirror="R90") + rotate=90) self.add_via(layers=("metal2", "via2", "metal3"), offset=[self.bank_select_or_position.x + drc["minwidth_metal1"], A_y_offset], - mirror="R90") + rotate=90) else: # connect A to last A, both are tri_en_bar via_offset = vector(self.bank_select_or_position.x @@ -1211,17 +1096,17 @@ class bank(design.design): A_y_offset) self.add_via(layers=("metal1", "via1", "metal2"), offset=via_offset, - mirror="R90") + rotate=90) self.add_via(layers=("metal2", "via2", "metal3"), offset=via_offset, - mirror="R90") + rotate=90) start = via_offset + vector(0, 0.5 * self.m1m2_via.width) mid = [self.left_vdd_x_offset - self.left_vdd_x_offset - drc["minwidth_metal2"] - drc["metal2_to_metal2"] + bank_select_line_xoffset, start.y] - correct_y = (2 * self.NOR2.A_position.y + drc["minwidth_metal1"] + correct_y = (2 * self.nor2.A_position.y + drc["minwidth_metal1"] - self.m1m2_via.width) end = start + vector(0, correct_y) self.add_wire(("metal3", "via2", "metal2"), [start, mid, end]) @@ -1230,108 +1115,23 @@ class bank(design.design): setattr(self,"{0}_position".format(self.control_signals[i]), [self.left_vdd_x_offset, A_y_offset]) - def route_power_rail_vdd(self): - """ Routing of VDD for all modules """ + def route_vdd_supply(self): + """ Route vdd for the precharge, sense amp, write_driver, data FF, tristate """ - # RIGHT HAND SIDE VDD RAIL CONNECTIONS - # Connecting Bitcell-array VDDs - for i,offset in enumerate(self.bitcell_array.vdd_positions): - if (i % 2 == 0): + for inst in [self.precharge_array_inst, self.sense_amp_array_inst, + self.write_driver_array_inst, self.msf_data_in_inst, + self.tri_gate_array_inst]: + for vdd_pin in inst.get_pins("vdd"): self.add_rect(layer="metal1", - offset=offset - vector(0, 0.5 * drc["minwidth_metal1"]), - width=self.right_vdd_x_offset - offset.x, + offset=vdd_pin.ll(), + width=self.right_vdd_x_offset - vdd_pin.lx(), height=drc["minwidth_metal1"]) - # Connecting Pre-charge VDD - for offset in self.precharge_array.vdd_positions: - self.add_rect(layer="metal1", - offset=self.precharge_array_position + offset, - width=(self.right_vdd_x_offset - offset.x - - self.precharge_array_position.x), - height=drc["minwidth_metal1"]) + return - # Connecting Sense Amp VDD - for offset in self.sens_amp_array.vdd_positions: - self.add_rect(layer="metal1", - offset=self.sens_amp_array_position + offset, - width=(self.right_vdd_x_offset - offset.x - - self.sens_amp_array_position.x), - height=drc["minwidth_metal1"]) - - # Connecting Write Driver VDD - for offset in self.write_driver_array.vdd_positions: - self.add_rect(layer="metal1", - offset=self.write_driver_array_position + offset, - width=(self.right_vdd_x_offset - offset.x - - self.write_driver_array_position.x), - height=drc["minwidth_metal1"]) - - # Connecting msf_data_in VDD - for offset in self.msf_data_in.vdd_positions: - self.add_rect(layer="metal1", - offset=(self.ms_flop_data_in_offset + offset - -vector(0, 0.5 * drc["minwidth_metal1"])), - width=self.right_vdd_x_offset \ - - (self.ms_flop_data_in_offset.x + offset.x), - height=drc["minwidth_metal1"]) - - # Connecting tri_gate VDD - for offset in self.tri_gate_array.vdd_positions: - self.add_rect(layer="metal1", - offset=(self.tri_gate_array_offset + offset.scale(1,-1) - - vector(0, 0.5 * drc["minwidth_metal1"])), - width=(self.right_vdd_x_offset - offset.x - - self.tri_gate_array_offset.x), - height=drc["minwidth_metal1"]) - - # LEFT HAND SIDE VDD RAIL CONNECTIONS - - # Connecting decoder VDD - for i, offset in enumerate(self.decoder.vdd_positions): - decoder_vdd_offset = self.decoder_position + offset - if(i % 2 == 0): - self.add_rect(layer="metal1", - offset=decoder_vdd_offset, - width=self.left_vdd_x_offset - decoder_vdd_offset.x, - height=drc["minwidth_metal1"]) - - # Connecting pre-decoder vdds - for offset in self.decoder.pre_decoder_vdd_positions: - preedecoder_vdd_offset = self.decoder_position + offset - self.add_rect(layer="metal1", - offset=[self.left_vdd_x_offset, - preedecoder_vdd_offset.y], - width=preedecoder_vdd_offset.x - self.left_vdd_x_offset, - height=drc["minwidth_metal1"]) - - # Connecting column_decoder vdd [Its the 2:4 decoder] - if(self.col_addr_size == 2): - col_vdd_offset = self.col_decoder_position + self.col_decoder.vdd_position - self.add_rect(layer="metal1", - offset=[self.left_vdd_x_offset, - col_vdd_offset.y], - width=col_vdd_offset.x - self.left_vdd_x_offset, - height=drc["minwidth_metal1"]) - - # Connecting address Flip-flop VDD - for offset in self.msf_address.vdd_positions: - ms_addres_gnd_y = (self.msf_address_offset.y - self.msf_address.width - - 0.5 * drc["minwidth_metal1"]) - y_offset = ms_addres_gnd_y - 2.5*drc["minwidth_metal1"] - vdd_connection = vector(self.left_vdd_x_offset, y_offset) - mid1 = vdd_connection - mid2 = vector(self.msf_address_offset.x + offset.y, - mid1.y) + vector(0, 0.5 * drc["minwidth_metal1"]) - mid3 = vector(mid2.x, ms_addres_gnd_y) + vector(0, 0.5 * drc["minwidth_metal1"]) - # FIXME: This offset may be wrong during path updates - self.add_path(layer="metal1", - coordinates=[mid1, mid2, mid3], - width=drc["minwidth_metal1"]) - - - # Connecting bank_select_and2_array vdd + # Connect bank_select_and2_array vdd if(self.num_banks > 1): - for i in range(self.number_of_control_lines): + for i in range(self.num_control_lines): if(i % 2): self.add_rect(layer="metal1", offset=[self.left_vdd_x_offset, @@ -1342,141 +1142,25 @@ class bank(design.design): - self.left_vdd_x_offset), height=drc["minwidth_metal1"]) - def route_power_rail_gnd(self): - """ Routing of GND for all modules """ - # FIRST HORIZONTAL GND RAIL BETWEEN PRECHARGE AND BITCELL - yoffset = self.bitcell_array.height + 2*drc["minwidth_metal1"] - # Add gnd via - self.add_via(layers=("metal1", "via1", "metal2"), - offset=[self.left_gnd_x_offset, yoffset], - size=(2,1)) - self.add_rect(layer="metal1", - offset=[self.left_gnd_x_offset, yoffset], - width=self.bitcell_array.width - self.left_gnd_x_offset, - height=drc["minwidth_metal1"]) - - for offset in self.bitcell_array.gnd_positions: - #print self.bitcell_array.gnd_positions - self.add_rect(layer="metal2", - offset=[offset.x - 0.5*drc["minwidth_metal2"], - self.bitcell_array.height], - width=drc["minwidth_metal2"], - height= yoffset + drc["minwidth_metal1"] \ - - self.bitcell_array.height) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=[offset.x + drc["minwidth_metal2"], yoffset], - mirror="R90") - - # GND connectiontions for the left side of bitcell-array - self.add_rect(layer="metal2", - offset=[-drc["minwidth_metal2"], 0], - width=drc["minwidth_metal2"], - height=yoffset + drc["minwidth_metal1"]) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=[0, yoffset], - mirror="R90") - - # LEFT HAND SIDE GND RAIL CONNECTIONS - # Connections of Tri_gate GND to the left hand GND rail - - # This is only used to compute teh sizes below - gnd_contact = contact(layer_stack=("metal1", "via1", "metal2"), - dimensions=(2, 1)) - - x_off = (self.left_gnd_x_offset + self.power_rail_width - - gnd_contact.width) - y_off = (self.tri_gate_array_offset.y - self.tri_gate_array.height - - drc["minwidth_metal1"]) - tri_gate_gnd_offset = vector(x_off, y_off) - self.add_rect(layer="metal1", - offset=tri_gate_gnd_offset, - width=(self.tri_gate_array_offset.x - + self.tri_gate_array.width - - tri_gate_gnd_offset.x), - height=drc["minwidth_metal1"]) - # Add gnd via - self.add_via(layers=("metal1", "via1", "metal2"), - offset=tri_gate_gnd_offset, - size=(2,1)) - - for offset in self.tri_gate_array.gnd_positions: - tri_gate_gnd_position = vector(self.tri_gate_array_offset.x + offset.x, - tri_gate_gnd_offset.y) - offset = tri_gate_gnd_position - vector(0.5 * self.m1m2_via.width, 0) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=offset) - - # Connecting decoder GND - for i,offset in enumerate(self.wordline_driver.gnd_positions): - wordline_driver_gnd_offset = self.wordline_driver_position + offset - even_row = (i % 2 == 0 and i != 0) - last_row = (i == self.num_rows - 1) - if even_row or last_row: - if even_row: - correct = vector(0,0) - # Connection of the last GND rail [The top most gnd of decoder] - if last_row: - correct = vector(0, drc["minwidth_metal1"]) - self.add_rect(layer="metal2", - offset=wordline_driver_gnd_offset - correct, - width=(self.left_gnd_x_offset - - wordline_driver_gnd_offset.x), - height=drc["minwidth_metal2"]) + def route_gnd_supply(self): + """ Route gnd for the precharge, sense amp, write_driver, data FF, tristate """ + # precharge is connected by abutment + # msf_data_in is by abutment + for inst in [ self.tri_gate_array_inst, self.sense_amp_array_inst, self.write_driver_array_inst]: + for gnd_pin in inst.get_pins("gnd"): + if gnd_pin.layer != "metal1": + continue + # route to the right hand side of the big rail to reduce via overlaps + gnd_offset = vector(self.gnd_x_offset+self.gnd_rail_width-self.m1m2_via.width, gnd_pin.by()) + self.add_rect(layer="metal1", + offset=gnd_offset, + width=gnd_pin.lx() - self.gnd_x_offset, + height=drc["minwidth_metal1"]) self.add_via(layers=("metal1", "via1", "metal2"), - offset=[self.wordline_driver_position.x - + self.wordline_driver.width - + 0.5*drc["minwidth_metal2"], - wordline_driver_gnd_offset.y - - correct.y], - mirror="R90") + offset=gnd_offset) - # Connecting Pre-decoder gnd rail - for i in range(len(self.decoder.pre_decoder_gnd_positions)): - offset = self.decoder.pre_decoder_gnd_positions[i] - preedecoder_gnd_offset = self.decoder_position + offset - self.add_rect(layer="metal1", - offset=preedecoder_gnd_offset, - width=self.left_gnd_x_offset - preedecoder_gnd_offset.x, - height=drc["minwidth_metal1"]) - # Add gnd via - self.add_via(layers=("metal1", "via1", "metal2"), - offset=[self.left_gnd_x_offset, - preedecoder_gnd_offset.y + drc["minwidth_metal1"]], - mirror="MX", - size=(2,1)) - - # Connecting column_decoder gnd [Its the 2:4 decoder] - if(self.col_addr_size == 2): - col_gnd_offset = self.col_decoder_position + self.col_decoder.gnd_position - self.add_rect(layer="metal1", - offset=col_gnd_offset, - width=self.left_gnd_x_offset - col_gnd_offset.x, - height=drc["minwidth_metal1"]) - # Add gnd via - self.add_via(layers=("metal1", "via1", "metal2"), - offset=[self.left_gnd_x_offset, col_gnd_offset.y], - size=(2,1)) - - # Connecting address FF GND - for offset in self.msf_address.gnd_positions: - correct = vector(self.msf_address.height, - - offset.x - 0.5*drc["minwidth_metal1"]) - ms_addres_gnd_offset = self.msf_address_offset + correct - self.add_via(layers=("metal1", "via1", "metal2"), - offset=(ms_addres_gnd_offset - + vector(drc["minwidth_metal1"], 0)), - mirror="R90") - self.add_rect(layer="metal1", - offset=ms_addres_gnd_offset, - width=self.left_gnd_x_offset - ms_addres_gnd_offset.x, - height=drc["minwidth_metal1"]) - # Add gnd via - self.add_via(layers=("metal1", "via1", "metal2"), - offset=[self.left_gnd_x_offset, - ms_addres_gnd_offset.y], - size=(2,1)) - - # Connecting bank_select_or2_array gnd + + # Connect bank_select_or2_array gnd if(self.num_banks > 1): self.bank_select_inv_position self.add_rect(layer="metal1", @@ -1487,8 +1171,8 @@ class bank(design.design): height=drc["minwidth_metal1"]) x_offset = (self.bank_select_or_position.x - + self.NOR2.width + self.inv4x.width) - for i in range(self.number_of_control_lines): + + self.nor2.width + self.inv4x.width) + for i in range(self.num_control_lines): if(i % 2 == 0): y_offset = self.bank_select_or_position.y + i*self.inv.height \ - 0.5*drc["minwidth_metal1"] @@ -1505,8 +1189,53 @@ class bank(design.design): self.add_via(layers=("metal1", "via1", "metal2"), offset=[x_offset + drc["minwidth_metal1"], y_offset], - mirror="R90") + rotate=90) + def add_control_pins(self): + """ Add the control signal input pins """ + + if self.num_banks==1: + # If we are a single bank, just add duplicate pin shapes + # on the existing the control bus + for ctrl in self.control_signals: + x_offset = self.central_line_xoffset[ctrl] + self.add_layout_pin(text=ctrl, + layer="metal2", + offset=vector(x_offset, self.min_point), + width=self.m2_width, + height=self.height) + else: + # If we are gating the signals, they must be the inputs to the gating logic + # Then route the outputs to the control bus + self.route_bank_select_or2_gates() + pass + + 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_position = vector(self.rail_1_x_offsets[rail], in_pin.y) + self.add_wire(("metal3","via2","metal2"),[in_pin, rail_position, rail_position - vector(0,self.m2_pitch)]) + # Bring it up to M2 for M2/M3 routing + self.add_via(layers=("metal1","via1","metal2"), + offset=in_pin + self.m1m2_via_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_position = vector(self.rail_1_x_offsets[rail], in_pin.y) + self.add_wire(("metal3","via2","metal2"),[in_pin, rail_position, rail_position - vector(0,self.m2_pitch)]) + self.add_via(layers=("metal1","via1","metal2"), + offset=in_pin + self.m1m2_via_offset, + rotate=90) + self.add_via(layers=("metal2","via2","metal3"), + offset=in_pin + self.m2m3_via_offset, + rotate=90) + def delay(self, slew, load): """ return analytical delay of the bank""" msf_addr_delay = self.msf_address.delay(slew, self.decoder.input_load()) @@ -1517,7 +1246,7 @@ class bank(design.design): bitcell_array_delay = self.bitcell_array.delay(word_driver_delay.slew) - bl_t_data_out_delay = self.sens_amp_array.delay(bitcell_array_delay.slew, + bl_t_data_out_delay = self.sense_amp_array.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. diff --git a/compiler/bitcell.py b/compiler/bitcell.py index 8929324c..2aabde07 100644 --- a/compiler/bitcell.py +++ b/compiler/bitcell.py @@ -11,15 +11,17 @@ class bitcell(design.design): library. """ - pins = ["BL", "BR", "WL", "vdd", "gnd"] - chars = utils.auto_measure_libcell(pins, "cell_6t", GDS["unit"], layer["boundary"]) + pin_names = ["BL", "BR", "WL", "vdd", "gnd"] + (width,height) = utils.get_libcell_size("cell_6t", GDS["unit"], layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "cell_6t", GDS["unit"], layer["boundary"]) - def __init__(self, name="cell_6t"): - design.design.__init__(self, name) + def __init__(self): + design.design.__init__(self, "cell_6t") debug.info(2, "Create bitcell object") - self.width = bitcell.chars["width"] - self.height = bitcell.chars["height"] + self.width = bitcell.width + self.height = bitcell.height + self.pin_map = bitcell.pin_map def delay(self, slew, load=0, swing = 0.5): # delay of bit cell is not like a driver(from WL) diff --git a/compiler/bitcell_array.py b/compiler/bitcell_array.py index b14fd6ae..fce9d884 100644 --- a/compiler/bitcell_array.py +++ b/compiler/bitcell_array.py @@ -13,7 +13,7 @@ class bitcell_array(design.design): Connects the word lines and bit lines. """ - def __init__(self, name, cols, rows): + def __init__(self, cols, rows, name="bitcell_array"): design.design.__init__(self, name) debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols)) @@ -23,11 +23,15 @@ class bitcell_array(design.design): c = reload(__import__(OPTS.config.bitcell)) self.mod_bitcell = getattr(c, OPTS.config.bitcell) - self.bitcell_chars = self.mod_bitcell.chars + self.cell = self.mod_bitcell() + self.add_mod(self.cell) + self.height = self.row_size*self.cell.height + self.width = self.column_size*self.cell.width + self.add_pins() self.create_layout() - self.add_labels() + self.add_layout_pins() self.DRC_LVS() def add_pins(self): @@ -40,26 +44,8 @@ class bitcell_array(design.design): self.add_pin("gnd") def create_layout(self): - self.create_cell() - self.setup_layout_constants() - self.add_cells() - self.offset_all_coordinates() - - def setup_layout_constants(self): - self.vdd_positions = [] - self.gnd_positions = [] - self.BL_positions = [] - self.BR_positions = [] - self.WL_positions = [] - self.height = self.row_size * self.cell.height - self.width = self.column_size * self.cell.width - - def create_cell(self): - self.cell = self.mod_bitcell() - self.add_mod(self.cell) - - def add_cells(self): xoffset = 0.0 + self.cell_inst = {} for col in range(self.column_size): yoffset = 0.0 for row in range(self.row_size): @@ -70,79 +56,110 @@ class bitcell_array(design.design): dir_key = "MX" else: tempy = yoffset - dir_key = "R0" + dir_key = "" - if OPTS.trim_noncritical == True: - if row == self.row_size - 1: - self.add_inst(name=name, - mod=self.cell, - offset=[xoffset, tempy], - mirror=dir_key) - self.connect_inst(["bl[{0}]".format(col), - "br[{0}]".format(col), - "wl[{0}]".format(row), - "vdd", - "gnd"]) - else: - self.add_inst(name=name, - mod=self.cell, - offset=[xoffset, tempy], - mirror=dir_key) - self.connect_inst(["bl[{0}]".format(col), - "br[{0}]".format(col), - "wl[{0}]".format(row), - "vdd", - "gnd"]) + self.cell_inst[row,col]=self.add_inst(name=name, + mod=self.cell, + offset=[xoffset, tempy], + mirror=dir_key) + self.connect_inst(["bl[{0}]".format(col), + "br[{0}]".format(col), + "wl[{0}]".format(row), + "vdd", + "gnd"]) yoffset += self.cell.height xoffset += self.cell.width - def add_labels(self): + + def add_layout_pins(self): + + # Our cells have multiple gnd pins for now. + # FIXME: fix for multiple vdd too + vdd_pin = self.cell.get_pin("vdd") + + # shift it up by the overlap amount (gnd_pin) too + # must find the lower gnd pin to determine this overlap + lower_y = self.cell.height + gnd_pins = self.cell.get_pins("gnd") + for gnd_pin in gnd_pins: + if gnd_pin.layer=="metal2" and gnd_pin.by()=self.cell.height: + self.add_layout_pin(text="gnd", + layer="metal2", + offset=gnd_pin.ll(), + width=gnd_pin.width(), + height=full_height) + # increments to the next column width offset.x += self.cell.width + offset.x = 0.0 + for row in range(self.row_size): + wl_pin = self.cell_inst[row,0].get_pin("WL") + vdd_pins = self.cell_inst[row,0].get_pins("vdd") + gnd_pins = self.cell_inst[row,0].get_pins("gnd") + + for gnd_pin in gnd_pins: + if gnd_pin.layer=="metal1": + self.add_layout_pin(text="gnd", + layer="metal1", + offset=gnd_pin.ll(), + width=full_width, + height=drc["minwidth_metal1"]) + + # add vdd label and offset + # only add to even rows to avoid duplicates + for vdd_pin in vdd_pins: + if row % 2 == 0 and vdd_pin.layer=="metal1": + self.add_layout_pin(text="vdd", + layer="metal1", + offset=vdd_pin.ll(), + width=full_width, + height=drc["minwidth_metal1"]) + + # add wl label and offset + self.add_layout_pin(text="wl[{0}]".format(row), + layer="metal1", + offset=wl_pin.ll(), + width=full_width, + height=wl_pin.height()) + + # increments to the next row height + offset.y += self.cell.height + def delay(self, slew, load=0): from tech import drc wl_wire = self.gen_wl_wire() diff --git a/compiler/calibre.py b/compiler/calibre.py index 1a8c7279..8f5bf473 100644 --- a/compiler/calibre.py +++ b/compiler/calibre.py @@ -15,7 +15,7 @@ A calibre DRC runset file contains, at the minimum, the following information: *drcLayoutPrimary: cell_6t *drcLayoutSystem: GDSII *drcResultsformat: ASCII -*drcResultsFile: cell_6t.drc.db +*drcResultsFile: cell_6t.drc.results *drcSummaryFile: cell_6t.drc.summary *cmnFDILayerMapFile: ./layer.map *cmnFDIUseLayerMap: 1 @@ -28,7 +28,7 @@ To open the results, you can do this: calibredrv cell_6t.gds Select Verification->Start RVE. -Select the cell_6t.drc.db file. +Select the cell_6t.drc.results file. Click on the errors and they will highlight in the design layout viewer. For LVS: @@ -44,7 +44,7 @@ For LVS: *lvsPowerNames: vdd *lvsGroundNames: vss *lvsIgnorePorts: 1 -*lvsERCDatabase: cell_6t.erc.db +*lvsERCDatabase: cell_6t.erc.results *lvsERCSummaryFile: cell_6t.erc.summary *lvsReportFile: cell_6t.lvs.report *lvsMaskDBFile: cell_6t.maskdb @@ -82,7 +82,7 @@ def run_drc(name, gds_name): 'drcLayoutPrimary': name, 'drcLayoutSystem': 'GDSII', 'drcResultsformat': 'ASCII', - 'drcResultsFile': OPTS.openram_temp + name + ".drc.db", + 'drcResultsFile': OPTS.openram_temp + name + ".drc.results", 'drcSummaryFile': OPTS.openram_temp + name + ".drc.summary", 'cmnFDILayerMapFile': drc["layer_map"], 'cmnFDIUseLayerMap': 1 @@ -153,7 +153,7 @@ def run_lvs(name, gds_name, sp_name): 'lvsIncludeSVRFCmds': 1, 'lvsSVRFCmds': '{VIRTUAL CONNECT NAME VDD? GND? ?}', 'lvsIgnorePorts': 1, - 'lvsERCDatabase': OPTS.openram_temp + name + ".erc.db", + 'lvsERCDatabase': OPTS.openram_temp + name + ".erc.results", 'lvsERCSummaryFile': OPTS.openram_temp + name + ".erc.summary", 'lvsReportFile': OPTS.openram_temp + name + ".lvs.report", 'lvsMaskDBFile': OPTS.openram_temp + name + ".maskdb", diff --git a/compiler/contact.py b/compiler/contact.py index ed47ed41..36b4faef 100644 --- a/compiler/contact.py +++ b/compiler/contact.py @@ -31,16 +31,11 @@ class contact(design.design): self.create_contact_array() self.create_first_layer_enclosure() self.create_second_layer_enclosure() - self.offset_all_coordinates() - - def offset_all_coordinates(self): - coordinate = self.find_lowest_coords() - self.offset_attributes(coordinate) - self.translate(coordinate) - + self.height = max(obj.offset.y + obj.height for obj in self.objs) self.width = max(obj.offset.x + obj.width for obj in self.objs) + def setup_layers(self): (first_layer, via_layer, second_layer) = self.layer_stack self.first_layer_name = first_layer @@ -51,49 +46,50 @@ class contact(design.design): self.contact_width = drc["minwidth_{0}". format(self.via_layer_name)] self.contact_to_contact = drc["{0}_to_{0}".format(self.via_layer_name)] self.contact_pitch = self.contact_width + self.contact_to_contact - self.contact_array_width = self.contact_width \ - + (self.dimensions[0] - 1) * self.contact_pitch - self.contact_array_height = self.contact_width \ - + (self.dimensions[1] - 1) * self.contact_pitch + self.contact_array_width = self.contact_width + (self.dimensions[0] - 1) * self.contact_pitch + self.contact_array_height = self.contact_width + (self.dimensions[1] - 1) * self.contact_pitch # FIME break this up self.first_layer_horizontal_enclosure = max((drc["minwidth_{0}".format(self.first_layer_name)] - self.contact_array_width) / 2, drc["{0}_enclosure_{1}".format(self.first_layer_name, self.via_layer_name)]) self.first_layer_vertical_enclosure = max((drc["minarea_{0}".format(self.first_layer_name)] / (self.contact_array_width + 2 * self.first_layer_horizontal_enclosure) - self.contact_array_height) / 2, - (drc["minheight_{0}".format( - self.first_layer_name)] - self.contact_array_height) / 2, + (drc["minheight_{0}".format(self.first_layer_name)] - self.contact_array_height) / 2, drc["{0}_extend_{1}".format(self.first_layer_name, self.via_layer_name)]) self.second_layer_horizontal_enclosure = max((drc["minwidth_{0}".format(self.second_layer_name)] - self.contact_array_width) / 2, drc["{0}_enclosure_{1}".format(self.second_layer_name, self.via_layer_name)]) self.second_layer_vertical_enclosure = max((drc["minarea_{0}".format(self.second_layer_name)] / (self.contact_array_width + 2 * self.second_layer_horizontal_enclosure) - self.contact_array_height) / 2, - (drc["minheight_{0}".format( - self.second_layer_name)] - self.contact_array_height) / 2, + (drc["minheight_{0}".format(self.second_layer_name)] - self.contact_array_height) / 2, drc["{0}_extend_{1}".format(self.second_layer_name, self.via_layer_name)]) + # offset for the via array + self.via_layer_position =vector(max(self.first_layer_horizontal_enclosure,self.second_layer_horizontal_enclosure), + max(self.first_layer_vertical_enclosure,self.second_layer_vertical_enclosure)) + # this is if the first and second layers are different + self.first_layer_position = vector(max(self.second_layer_horizontal_enclosure - self.first_layer_horizontal_enclosure,0), + max(self.second_layer_vertical_enclosure - self.first_layer_vertical_enclosure,0)) + self.second_layer_position = vector(max(self.first_layer_horizontal_enclosure - self.second_layer_horizontal_enclosure,0), + max(self.first_layer_vertical_enclosure - self.second_layer_vertical_enclosure,0)) def create_contact_array(self): """ Create the contact array at the origin""" - self.via_layer_position = vector(0, 0) for i in range(self.dimensions[1]): - offset = [0, 0 + self.contact_pitch * i] + offset = self.via_layer_position + vector(0, self.contact_pitch * i) for j in range(self.dimensions[0]): self.add_rect(layer=self.via_layer_name, offset=offset, width=self.contact_width, height=self.contact_width) - offset = [offset[0] + self.contact_pitch, offset[1]] + offset = offset + vector(self.contact_pitch,0) def create_first_layer_enclosure(self): width = self.first_layer_width = self.contact_array_width \ + 2 * self.first_layer_horizontal_enclosure height = self.first_layer_height = self.contact_array_height \ + 2 * self.first_layer_vertical_enclosure - offset = self.first_layer_position = vector(-self.first_layer_horizontal_enclosure, - -self.first_layer_vertical_enclosure) self.add_rect(layer=self.first_layer_name, - offset=offset, + offset=self.first_layer_position, width=width, height=height) @@ -102,9 +98,7 @@ class contact(design.design): + 2 * self.second_layer_horizontal_enclosure height = self.second_layer_height = self.contact_array_height \ + 2 * self.second_layer_vertical_enclosure - offset = self.second_layer_position = vector(-self.second_layer_horizontal_enclosure, - -self.second_layer_vertical_enclosure) self.add_rect(layer=self.second_layer_name, - offset=offset, + offset=self.second_layer_position, width=width, height=height) diff --git a/compiler/control_logic.py b/compiler/control_logic.py index 26eb6479..126bd8ef 100644 --- a/compiler/control_logic.py +++ b/compiler/control_logic.py @@ -2,14 +2,11 @@ from math import log import design from tech import drc, parameter import debug -from ms_flop_array import ms_flop_array -from wordline_driver import wordline_driver from contact import contact from pinv import pinv from nand_2 import nand_2 from nand_3 import nand_3 from nor_2 import nor_2 -from replica_bitline import replica_bitline import math from vector import vector from globals import OPTS @@ -22,7 +19,7 @@ class control_logic(design.design): def __init__(self, num_rows): """ Constructor """ design.design.__init__(self, "control_logic") - debug.info(1, "Creating %s" % self.name) + debug.info(1, "Creating {}".format(self.name)) self.num_rows = num_rows self.create_layout() @@ -34,660 +31,581 @@ class control_logic(design.design): self.setup_layout_offsets() self.add_modules() self.add_routing() - self.add_pin_labels() def create_modules(self): """ add all the required modules """ - c = reload(__import__(OPTS.config.ms_flop)) - self.mod_ms_flop = getattr(c, OPTS.config.ms_flop) - self.ms_flop = self.mod_ms_flop("ms_flop") - self.add_mod(self.ms_flop) - self.inv = pinv(nmos_width=drc["minwidth_tx"], - beta=parameter["pinv_beta"]) + input_lst =["csb","web","oeb"] + output_lst = ["s_en", "w_en", "tri_en", "tri_en_bar", "clk_bar"] + clk =["clk"] + rails = ["vdd", "gnd"] + for pin in input_lst + output_lst + clk + rails: + self.add_pin(pin) - self.add_mod(self.inv) - self.nand2 = nand_2(nmos_width=2 * drc["minwidth_tx"]) + self.nand2 = nand_2() self.add_mod(self.nand2) - self.NAND3 = nand_3(nmos_width=3 * drc["minwidth_tx"]) - self.add_mod(self.NAND3) - - # Special gates: 4x Inverter - self.inv4 = pinv(nmos_width=4 * drc["minwidth_tx"], - beta=parameter["pinv_beta"]) - self.add_mod(self.inv4) - - self.nor2 = nor_2(nmos_width=drc["minwidth_tx"]) + self.nand3 = nand_3() + self.add_mod(self.nand3) + self.nor2 = nor_2() self.add_mod(self.nor2) + + # Special gates: inverters for buffering + self.inv = self.inv1 = pinv() + self.add_mod(self.inv1) + self.inv2 = pinv(nmos_width=2*drc["minwidth_tx"]) + self.add_mod(self.inv2) + self.inv4 = pinv(nmos_width=4*drc["minwidth_tx"]) + self.add_mod(self.inv4) + self.inv8 = pinv(nmos_width=8*drc["minwidth_tx"]) + self.add_mod(self.inv8) + self.inv16 = pinv(nmos_width=16*drc["minwidth_tx"]) + self.add_mod(self.inv16) + + c = reload(__import__(OPTS.config.ms_flop_array)) + ms_flop_array = getattr(c, OPTS.config.ms_flop_array) self.msf_control = ms_flop_array(name="msf_control", columns=3, word_size=3) self.add_mod(self.msf_control) - self.replica_bitline = replica_bitline(name="replica_bitline", - rows=int(math.ceil(self.num_rows / 10.0))) + c = reload(__import__(OPTS.config.replica_bitline)) + replica_bitline = getattr(c, OPTS.config.replica_bitline) + self.replica_bitline = replica_bitline(rows=int(math.ceil(self.num_rows / 10.0))) self.add_mod(self.replica_bitline) - def add_pin_labels(self): - """ Add pins and labels after everything is done """ - input_lst =["CSb","WEb","OEb"] - output_lst = ["s_en", "w_en", "tri_en", "tri_en_bar", "clk_bar"] - clk =["clk"] - rails = ["vdd", "gnd"] - pin_lst = input_lst + output_lst + clk + rails - for pin in pin_lst: - self.add_pin(pin) - - # add label of input, output and clk in metal3 layer - input_lst =["CSb","WEb","OEb"] - output_lst = ["s_en", "w_en", "tri_en", "tri_en_bar", "clk_bar"] - for pin in input_lst + output_lst + ["clk"]: - self.add_label(text=pin, - layer="metal3", - offset=getattr(self, pin+"_position")) - # add label of vdd and gnd manually cause non-uniformed names and layers - self.add_label(text="vdd", - layer="metal1", - offset=self.vdd1_position) - self.add_label(text="vdd", - layer="metal2", - offset=self.vdd2_position) - self.add_label(text="gnd", - layer="metal2", - offset=self.gnd_position) def setup_layout_offsets(self): """ Setup layout offsets, determine the size of the busses etc """ - # This isn't for instantiating, but we use it to get the dimensions - m1m2_via = contact(layer_stack=("metal1", "via1", "metal2")) + # These aren't for instantiating, but we use them to get the dimensions + self.poly_contact = contact(layer_stack=("poly", "contact", "metal1")) + self.poly_contact_offset = vector(0.5*self.poly_contact.width,0.5*self.poly_contact.height) + self.m1m2_via = contact(layer_stack=("metal1", "via1", "metal2")) + self.m2m3_via = contact(layer_stack=("metal2", "via2", "metal3")) - # Vertical metal rail gap definition - self.metal2_extend_contact = (m1m2_via.second_layer_height - m1m2_via.contact_width) / 2 - self.gap_between_rails = self.metal2_extend_contact + drc["metal2_to_metal2"] - self.gap_between_rail_offset = self.gap_between_rails + drc["minwidth_metal2"] - self.via_shift = (m1m2_via.second_layer_width - m1m2_via.first_layer_width) / 2 + # M1/M2 routing pitch is based on contacted pitch + self.m1_pitch = max(self.m1m2_via.width,self.m1m2_via.height) + max(drc["metal1_to_metal1"],drc["metal2_to_metal2"]) + self.m2_pitch = max(self.m2m3_via.width,self.m2m3_via.height) + max(drc["metal2_to_metal2"],drc["metal3_to_metal3"]) - # used to shift contact when connecting to NAND3 C pin down - self.contact_shift = (m1m2_via.first_layer_width - m1m2_via.contact_width) / 2 + # Have the cell gap leave enough room to route an M1 wire. + # Some cells may have pwell/nwell spacing problems too when the wells are different heights. + self.cell_gap = max(self.m1_pitch,drc["pwell_to_nwell"]) + + # This corrects the offset pitch difference between M2 and M1 + self.offset_fix = vector(0.5*(drc["minwidth_metal2"]-drc["minwidth_metal1"]),0) - # Common parameters for rails - self.rail_width = drc["minwidth_metal2"] - self.rail_gap = 2 * drc["metal2_to_metal2"] - self.rail_offset_gap = self.rail_width + self.rail_gap + # Amount to shift a via from center-line path routing to it's offset + self.m1m2_via_offset = vector(self.m1m2_via.height,-0.5*drc["minwidth_metal2"]) + self.m2m3_via_offset = vector(self.m2m3_via.height,-0.5*drc["minwidth_metal3"]) + + # First RAIL Parameters: gnd, oe, oebar, cs, we, clk_buf, clk_bar + self.rail_1_start_x = 0 + self.num_rails_1 = 8 + self.rail_1_names = ["clk_buf", "gnd", "oe_bar", "cs", "we", "vdd", "oe", "clk_bar"] + self.overall_rail_1_gap = (self.num_rails_1 + 2) * self.m2_pitch + self.rail_1_x_offsets = {} - # First RAIL Parameters - self.num_rails_1 = 6 - self.overall_rail_1_gap = (self.num_rails_1 + 1) * self.rail_offset_gap - self.rail_1_x_offsets = [] + # Second RAIL Parameters: vdd + self.rail_2_start_x = 0 + self.num_rails_2 = 0 + self.rail_2_names = ["vdd"] + self.overall_rail_2_gap = (self.num_rails_2 + 2) * self.m2_pitch + self.rail_2_x_offsets = {} - # Second RAIL Parameters - self.num_rails_2 = 4 - self.overall_rail_2_gap = (self.num_rails_2 + 1) * self.rail_offset_gap - self.rail_2_x_offsets = [] + # GAP between main control and replica bitline + self.replica_bitline_gap = 2*self.m2_pitch - # GAP between main control and REPLICA BITLINE - self.replica_bitline_gap = self.rail_offset_gap * 2 - self.output_port_gap = 3 * drc["minwidth_metal3"] - self.logic_height = max(self.replica_bitline.width, 4 * self.inv.height) def add_modules(self): """ Place all the modules """ - self.add_msf_control() - self.set_msf_control_pins() - self.add_1st_row(self.output_port_gap) - self.add_2nd_row(self.output_port_gap + 2 * self.inv.height) + self.add_control_flops() + self.add_clk_buffer(0) + self.add_1st_row(0) + self.add_2nd_row(self.inv1.height) + self.add_3rd_row(2*self.inv1.height) + + self.add_control_routing() + self.add_rbl(0) + self.add_layout_pins() + + self.height = max(self.replica_bitline.width, 3 * self.inv1.height, self.msf_offset.y) + self.width = self.replica_bitline_offset.x + self.replica_bitline.height + + - # Height and width - self.height = self.logic_height + self.output_port_gap - self.width = self.offset_replica_bitline.x + self.replica_bitline.height def add_routing(self): """ Routing between modules """ - self.add_msf_control_routing() - self.add_1st_row_routing() - self.add_2nd_row_routing() - self.add_vdd_routing() - self.add_gnd_routing() - self.add_input_routing() + self.add_clk_routing() + self.add_trien_routing() + self.add_rblk_routing() + self.add_wen_routing() + self.add_sen_routing() self.add_output_routing() + self.add_supply_routing() - def add_msf_control(self): - """ ADD ARRAY OF MS_FLOP""" - self.offset_msf_control = vector(0, self.logic_height + self.output_port_gap) - self.add_inst(name="msf_control", - mod=self.msf_control, - offset=self.offset_msf_control, - mirror="R270") + def add_control_flops(self): + """ Add the control signal flops for OEb, WEb, CSb. """ + self.msf_offset = vector(0, self.inv.height+self.msf_control.width+2*self.m2_pitch) + self.msf=self.add_inst(name="msf_control", + mod=self.msf_control, + offset=self.msf_offset, + rotate=270) # don't change this order. This pins are meant for internal connection of msf array inside the control logic. # These pins are connecting the msf_array inside of control_logic. - temp = ["CSb", "WEb", "OEb", "CS_bar", "CS", "WE_bar", - "WE", "OE_bar", "OE", "clk", "vdd", "gnd"] + temp = ["oeb", "oe_bar", "oe", + "csb", "cs_bar", "cs", + "web", "we_bar", "we", + "clk_buf", "vdd", "gnd"] self.connect_inst(temp) - def set_msf_control_pins(self): - # msf_control inputs - correct = vector(0, 0.5 * drc["minwidth_metal2"]) - def translate_inputs(pt1,pt2): - return pt1 + pt2.rotate_scale(1,-1) - correct + def add_rbl(self,y_off): + """ Add the replica bitline """ + + self.replica_bitline_offset = vector(self.rail_2_end_x , y_off) + self.rbl=self.add_inst(name="replica_bitline", + mod=self.replica_bitline, + offset=self.replica_bitline_offset, + mirror="MX", + rotate=90) + self.connect_inst(["rblk", "pre_s_en", "vdd", "gnd"]) + + def add_layout_pins(self): + """ Add the input/output layout pins. """ - # msf_control outputs - def translate_outputs(pt1,pt2): - return pt1 - correct + vector(self.msf_control.height,- pt2.x) + # Top to bottom: CS WE OE signal groups + pin_set = ["oeb","csb","web"] + for (i,pin_name) in zip(range(3),pin_set): + subpin_name="din[{}]".format(i) + pin=self.msf.get_pin(subpin_name) + #pin=self.msf_control.get_pin("din[{}]".format(i)) + self.add_layout_pin(text=pin_name, + layer="metal2", + offset=pin.ll(), + width=pin.width(), + height=pin.height()) - # set CSS WE OE signal groups(in, out, bar) - pt1 = self.offset_msf_control - pin_set = ["CSb","WEb","OEb"] - pt2in = self.msf_control.din_positions[0:len(pin_set)] - pt2out = self.msf_control.dout_positions[0:len(pin_set)] - pt2bar = self.msf_control.dout_bar_positions[0:len(pin_set)] - for i in range(len(pin_set)): - value = translate_inputs(pt1,pt2in[i]) - setattr(self,"msf_control_"+pin_set[i]+"_position",value) - value = translate_outputs(pt1,pt2out[i]) - setattr(self,"msf_control_"+pin_set[i][0:2]+"_bar_position",value) - value = translate_outputs(pt1,pt2bar[i]) - setattr(self,"msf_control_"+pin_set[i][0:2]+"_position",value) + pin=self.clk_inv1.get_pin("A") + self.add_layout_pin(text="clk", + layer="metal1", + offset=pin.ll(), + width=pin.width(), + height=pin.height()) - # clk , vdd - base = self.offset_msf_control - vector(0.5 * drc["minwidth_metal2"], 0) - msf_clk = self.msf_control.clk_positions[0].rotate_scale(1,-1) - self.msf_control_clk_position = base + msf_clk - msf_vdd = self.msf_control.vdd_positions[0].rotate_scale(1,-1) - self.msf_control_vdd_position = base + msf_vdd + pin=self.clk_inv1.get_pin("gnd") + self.add_layout_pin(text="gnd", + layer="metal1", + offset=pin.ll(), + width=self.width) - # gnd - self.msf_control_gnd_positions = [] - for gnd_offset in self.msf_control.gnd_positions: - offset = self.offset_msf_control + vector(self.msf_control.height, - - gnd_offset.x) - self.msf_control_gnd_positions.append(offset - correct) + pin=self.clk_inv1.get_pin("vdd") + self.add_layout_pin(text="vdd", + layer="metal1", + offset=pin.ll(), + width=self.width) + + def add_clk_buffer(self,y_off): + """ Add the multistage clock buffer below the control flops """ + # 4 stage clock buffer + self.clk_inv1_offset = vector(0, y_off) + self.clk_inv1=self.add_inst(name="inv_clk1_bar", + mod=self.inv2, + offset=self.clk_inv1_offset) + self.connect_inst(["clk", "clk1_bar", "vdd", "gnd"]) + self.clk_inv2_offset = self.clk_inv1_offset + vector(self.inv2.width,0) + self.clk_inv2=self.add_inst(name="inv_clk2", + mod=self.inv4, + offset=self.clk_inv2_offset) + self.connect_inst(["clk1_bar", "clk2", "vdd", "gnd"]) + self.clk_bar_offset = self.clk_inv2_offset + vector(self.inv4.width,0) + self.clk_bar=self.add_inst(name="inv_clk_bar", + mod=self.inv8, + offset=self.clk_bar_offset) + self.connect_inst(["clk2", "clk_bar", "vdd", "gnd"]) + self.clk_buf_offset = self.clk_bar_offset + vector(self.inv8.width,0) + self.clk_buf=self.add_inst(name="inv_clk_buf", + mod=self.inv16, + offset=self.clk_buf_offset) + self.connect_inst(["clk_bar", "clk_buf", "vdd", "gnd"]) + + # This is the first rail offset + self.rail_1_start_x = max(self.msf_offset.x + self.msf_control.height,self.clk_buf_offset.x+self.inv16.width) def add_1st_row(self,y_off): - # inv1 with clk as gate input. - msf_control_rotate_x = self.offset_msf_control.x + self.msf_control.height - self.offset_inv1 = vector(msf_control_rotate_x - self.inv4.width, y_off) - self.add_inst(name="clk_inverter", - mod=self.inv4, - offset=self.offset_inv1) - self.connect_inst(["clk", "clk_bar", "vdd", "gnd"]) - # set pin offset as attr - self.inv1_A_position = self.offset_inv1 + self.inv4.A_position.scale(0,1) - base = self.offset_inv1 + vector(self.inv4.width, 0) - for pin in ["Z_position", "vdd_position", "gnd_position"]: - setattr(self, "inv1_"+pin, base + getattr(self.inv4, pin).scale(0,1)) - # nor2 - self.offset_nor2 = vector(self.nor2.width + 2 * drc["minwidth_metal3"], - y_off) - self.add_inst(name="nor2", - mod=self.nor2, - offset=self.offset_nor2, - mirror="MY") - self.connect_inst(["clk", "OE_bar", "tri_en", "vdd", "gnd"]) - self.set_nand2_nor2_pin("nor2",[-1,1]) + x_off = self.rail_1_start_x + self.overall_rail_1_gap - x_off = msf_control_rotate_x + self.overall_rail_1_gap - self.nand_array_position = vector(x_off, y_off) + # input: OE, clk_bar,CS output: rblk_bar + self.rblk_bar_offset = vector(x_off, y_off) + self.rblk_bar=self.add_inst(name="nand3_rblk_bar", + mod=self.nand3, + offset=self.rblk_bar_offset) + self.connect_inst(["clk_bar", "oe", "cs", "rblk_bar", "vdd", "gnd"]) + x_off += self.nand3.width - # nand2_1 input: OE, clk_bar output: tri_en_bar - self.offset_nand2 = self.nand_array_position - self.add_inst(name="nand2_tri_en", - mod=self.nand2, - offset=self.offset_nand2) - self.connect_inst(["OE", "clk_bar", "tri_en_bar", "vdd", "gnd"]) - # set pin offset as attr - self.set_nand2_nor2_pin("nand2",[1,1]) - - # REPLICA BITLINE - base_x = self.nand_array_position.x + self.NAND3.width + 3 * self.inv.width - total_rail_gap = self.rail_offset_gap + self.overall_rail_2_gap - x_off = base_x + total_rail_gap + self.replica_bitline_gap - self.offset_replica_bitline = vector(x_off, y_off) - self.add_inst(name="replica_bitline", - mod=self.replica_bitline, - offset=self.offset_replica_bitline, - mirror="MX", - rotate=90) - self.connect_inst(["rblk", "pre_s_en", "vdd", "gnd"]) - - # BUFFER INVERTERS FOR S_EN - # inv_4 input: input: pre_s_en_bar, output: s_en - self.offset_inv4 = vector(base_x - 2 * self.inv.width, y_off) - self.add_inst(name="inv_s_en1", - mod=self.inv, - offset=self.offset_inv4, - mirror="MY") - self.connect_inst(["pre_s_en_bar", "s_en", "vdd", "gnd"]) - self.set_inv2345_pins(inv_name="inv4", inv_scale=[-1, 1]) - - # inv_5 input: pre_s_en, output: pre_s_en_bar - self.offset_inv5 = vector(base_x - self.inv.width, y_off) - self.add_inst(name="inv_s_en2", - mod=self.inv, - offset=self.offset_inv5, - mirror="MY") - self.connect_inst(["pre_s_en", "pre_s_en_bar", "vdd", "gnd"]) - self.set_inv2345_pins(inv_name="inv5", inv_scale=[-1, 1]) - - # set pin offset as attr - pin_offset = self.replica_bitline.en_input_offset.rotate() - self.replica_en_offset = self.offset_replica_bitline + pin_offset - pin_offset = self.replica_bitline.out_offset.rotate() - self.replica_out_offset = self.offset_replica_bitline + pin_offset + # input: rblk_bar, output: rblk + self.rblk_offset = vector(x_off, y_off) + self.rblk=self.add_inst(name="inv_rblk", + mod=self.inv1, + offset=self.rblk_offset) + self.connect_inst(["rblk_bar", "rblk", "vdd", "gnd"]) + #x_off += self.inv1.width + + self.row_1_width = x_off def add_2nd_row(self, y_off): - # Nand3_1 input: OE, clk_bar,CS output: rblk_bar - self.offset_nand3_1 = vector(self.nand_array_position.x, y_off) - self.add_inst(name="NAND3_rblk_bar", - mod=self.NAND3, - offset=self.offset_nand3_1, - mirror="MX") - self.connect_inst(["clk_bar", "OE", "CS", "rblk_bar", "vdd", "gnd"]) - # set pin offset as attr - self.set_Nand3_pins(nand_name = "nand3_1",nand_scale = [0,-1]) + # start after first rails + x_off = self.rail_1_start_x + self.overall_rail_1_gap + y_off += self.inv1.height - # Nand3_2 input: WE, clk_bar,CS output: w_en_bar - self.offset_nand3_2 = vector(self.nand_array_position.x, y_off) - self.add_inst(name="NAND3_w_en_bar", - mod=self.NAND3, - offset=self.offset_nand3_2, - mirror="RO") - self.connect_inst(["clk_bar", "WE", "CS", "w_en_bar", "vdd", "gnd"]) - # set pin offset as attr - self.set_Nand3_pins(nand_name = "nand3_2",nand_scale = [0,1]) + # input: clk_buf, OE_bar output: tri_en + self.tri_en_offset = vector(x_off, y_off) + self.tri_en=self.add_inst(name="nor2_tri_en", + mod=self.nor2, + offset=self.tri_en_offset, + mirror="MX") + self.connect_inst(["clk_buf", "oe_bar", "tri_en", "vdd", "gnd"]) + x_off += self.nor2.width + self.cell_gap + + # input: OE, clk_bar output: tri_en_bar + self.tri_en_bar_offset = vector(x_off,y_off) + self.tri_en_bar=self.add_inst(name="nand2_tri_en", + mod=self.nand2, + offset=self.tri_en_bar_offset, + mirror="MX") + self.connect_inst(["oe", "clk_bar", "tri_en_bar", "vdd", "gnd"]) + x_off += self.nand2.width - # connect nand2 and nand3 to inv - nand3_to_inv_connection_height = self.NAND3.Z_position.y- self.inv.A_position.y+ drc["minwidth_metal1"] - self.add_rect(layer="metal1", - offset=self.nand3_1_Z_position, - width=drc["minwidth_metal1"], - height=nand3_to_inv_connection_height) - self.add_rect(layer="metal1", - offset=self.nand3_2_Z_position + vector(0,drc["minwidth_metal1"]), - width=drc["minwidth_metal1"], - height=-nand3_to_inv_connection_height) + x_off += self.inv1.width + self.cell_gap + + # BUFFER INVERTERS FOR S_EN + # input: input: pre_s_en_bar, output: s_en + self.s_en_offset = vector(x_off, y_off) + self.s_en=self.add_inst(name="inv_s_en", + mod=self.inv1, + offset=self.s_en_offset, + mirror="XY") + self.connect_inst(["pre_s_en_bar", "s_en", "vdd", "gnd"]) + x_off += self.inv1.width - # inv_2 input: rblk_bar, output: rblk - x_off = self.nand_array_position.x + self.NAND3.width - self.offset_inv2 = vector(x_off, y_off) - self.add_inst(name="inv_rblk", - mod=self.inv, - offset=self.offset_inv2, - mirror="MX") - self.connect_inst(["rblk_bar", "rblk", "vdd", "gnd"]) - # set pin offset as attr - self.set_inv2345_pins(inv_name="inv2", inv_scale=[1,-1]) + # input: pre_s_en, output: pre_s_en_bar + self.pre_s_en_bar_offset = vector(x_off, y_off) + self.pre_s_en_bar=self.add_inst(name="inv_pre_s_en_bar", + mod=self.inv1, + offset=self.pre_s_en_bar_offset, + mirror="XY") + self.connect_inst(["pre_s_en", "pre_s_en_bar", "vdd", "gnd"]) + #x_off += self.inv1.width + + + self.row_2_width = x_off + + def add_3rd_row(self, y_off): + # start after first rails + x_off = self.rail_1_start_x + self.overall_rail_1_gap + + # This prevents some M2 outputs from overlapping (hack) + x_off += self.inv1.width + + # input: WE, clk_bar, CS output: w_en_bar + self.w_en_bar_offset = vector(x_off, y_off) + self.w_en_bar=self.add_inst(name="nand3_w_en_bar", + mod=self.nand3, + offset=self.w_en_bar_offset) + self.connect_inst(["clk_bar", "we", "cs", "w_en_bar", "vdd", "gnd"]) + x_off += self.nand3.width - # inv_3 input: w_en_bar, output: pre_w_en - self.offset_inv3 = self.offset_inv2 - self.add_inst(name="inv_w_en", - mod=self.inv, - offset=self.offset_inv3, - mirror="RO") + # input: w_en_bar, output: pre_w_en + self.pre_w_en_offset = vector(x_off, y_off) + self.pre_w_en=self.add_inst(name="inv_pre_w_en", + mod=self.inv1, + offset=self.pre_w_en_offset) self.connect_inst(["w_en_bar", "pre_w_en", "vdd", "gnd"]) - # set pin offset as attr - self.set_inv2345_pins(inv_name="inv3", inv_scale=[1, 1]) - + x_off += self.inv1.width + # BUFFER INVERTERS FOR W_EN - x_off = self.nand_array_position.x + self.NAND3.width + self.inv.width - self.offset_inv6 = vector(x_off, y_off) - self.add_inst(name="inv_w_en1", - mod=self.inv, - offset=self.offset_inv6, - mirror="RO") - self.connect_inst(["pre_w_en", "pre_w_en1", "vdd", "gnd"]) + self.pre_w_en_bar_offset = vector(x_off, y_off) + self.pre_w_en_bar=self.add_inst(name="inv_pre_w_en_bar", + mod=self.inv1, + offset=self.pre_w_en_bar_offset) + self.connect_inst(["pre_w_en", "pre_w_en_bar", "vdd", "gnd"]) + x_off += self.inv1.width - x_off = self.nand_array_position.x + self.NAND3.width + 2 * self.inv.width - self.offset_inv7 = [x_off, y_off] - self.add_inst(name="inv_w_en2", - mod=self.inv, - offset=self.offset_inv7, - mirror="RO") - self.connect_inst(["pre_w_en1", "w_en", "vdd", "gnd"]) - # set pin offset as attr - self.inv7_Z_position = self.offset_inv7 + vector(self.inv.width, - self.inv.Z_position[1]) + self.w_en_offset = vector(x_off, y_off) + self.w_en=self.add_inst(name="inv_w_en2", + mod=self.inv1, + offset=self.w_en_offset) + self.connect_inst(["pre_w_en_bar", "w_en", "vdd", "gnd"]) + #x_off += self.inv1.width - def set_nand2_nor2_pin(self,mod,scale): - offset = getattr (self, "offset_"+mod) - for pin in ["A","B"]: - pin_xy = getattr(getattr(self, mod), pin+"_position") - setattr(self, mod+"_1_"+pin+"_position", offset + pin_xy.scale(0,1)) - base = offset + vector(getattr(self, mod).width,0).scale(scale[0],0) - for pin in ["Z","vdd","gnd"]: - pin_xy = getattr(getattr(self, mod), pin+"_position") - setattr(self, mod+"_1_"+pin+"_position", base + pin_xy.scale(0,1)) + self.row_3_width = x_off - def set_Nand3_pins(self,nand_name,nand_scale): - base = getattr(self, "offset_"+nand_name) - extra = vector(0, drc["minwidth_metal1"]* (1 - nand_scale[1]) *0.5) - off1 = base - extra - off2 = base - extra + vector(self.NAND3.width, 0) - self.set_Nand3_pins_sub(nand_name,["A","B","C"],off1,[0,nand_scale[1]]) - self.set_Nand3_pins_sub(nand_name,["Z","vdd","gnd"],off2,[0,nand_scale[1]]) + def add_control_routing(self): + """ Route the vertical rails for internal control signals """ - def set_Nand3_pins_sub(self,nand_name,pin_lst,base,nand_scale): - for pin in pin_lst: - pin_xy = getattr(self.NAND3, pin+"_position").scale(0,nand_scale[1]) - setattr(self, nand_name+"_"+pin+"_position", base + pin_xy) - - def set_inv2345_pins(self,inv_name,inv_scale): - base_xy = getattr(self, "offset_"+inv_name) - correct= vector(0, (1-inv_scale[1]) * 0.5 * drc["minwidth_metal1"]) - # pin A - pin_xy = vector(0, self.inv4.A_position.y).scale(0,inv_scale[1]) - setattr(self, inv_name+"_A_position", base_xy + pin_xy - correct) - # Z, vdd, gnd - for pin in ["Z_position", "vdd_position", "gnd_position"]: - pin_xy = getattr(self.inv, pin).scale(0,inv_scale[1]) - rotated_pin_xy = vector(self.inv.width * inv_scale[0], 0) + pin_xy - setattr(self, inv_name+"_"+pin, base_xy + rotated_pin_xy - correct) - - def add_msf_control_routing(self): - # FIRST RAIL : MSF_CONTROL OUTPUT RAIL - rail1_start = vector(self.msf_control_WE_position.x, - self.output_port_gap) + control_rail_height = max(3 * self.inv1.height, self.msf_offset.y) + for i in range(self.num_rails_1): - correct = vector((i+1) * self.rail_offset_gap, 0) - offset = rail1_start + correct - self.add_rect(layer="metal2", - offset=offset, - width=drc["minwidth_metal2"], - height=self.logic_height) - self.rail_1_x_offsets.append(offset.x) + offset = vector(self.rail_1_start_x + (i+1) * self.m2_pitch,0) + if self.rail_1_names[i] in ["clk_bar", "vdd", "gnd"]: + self.add_layout_pin(text=self.rail_1_names[i], + layer="metal2", + offset=offset, + width=drc["minwidth_metal2"], + height=control_rail_height) + else: + self.add_rect(layer="metal2", + offset=offset, + width=drc["minwidth_metal2"], + height=control_rail_height) + self.rail_1_x_offsets[self.rail_1_names[i]]=offset.x + 0.5*drc["minwidth_metal2"] # center offset - rail2_start_x = (self.nand_array_position.x + self.NAND3.width - + 3 * self.inv.width + self.rail_offset_gap) + # pins are in order ["oeb","csb","web"] # 0 1 2 + self.connect_rail_from_left_m2m3(self.msf,"dout_bar[0]","oe") + self.connect_rail_from_left_m2m3(self.msf,"dout[0]","oe_bar") + self.connect_rail_from_left_m2m3(self.msf,"dout_bar[1]","cs") + self.connect_rail_from_left_m2m3(self.msf,"dout_bar[2]","we") + + # Connect the gnd and vdd of the control + gnd_pins = self.msf.get_pin("gnd") + for p in gnd_pins: + gnd_pin = p.rc() + gnd_rail_position = vector(self.rail_1_x_offsets["gnd"], gnd_pin.y) + self.add_wire(("metal1","via1","metal2"),[gnd_pin, gnd_rail_position, gnd_rail_position - vector(0,self.m2_pitch)]) + self.add_via(layers=("metal1","via1","metal2"), + offset=gnd_pin + self.m1m2_via_offset, + rotate=90) + + vdd_pin = self.msf.get_pin("vdd").bc() + clk_vdd_position = vector(vdd_pin.x,self.clk_buf.get_pin("vdd").uy()) + self.add_path("metal1",[vdd_pin,clk_vdd_position]) + + self.rail_2_start_x = max (self.row_1_width, self.row_2_width, self.row_3_width) for i in range(self.num_rails_2): - offset = vector(rail2_start_x + i * self.rail_offset_gap, - self.output_port_gap) - self.add_rect(layer="metal2", + offset = vector(self.rail_2_start_x + i * self.m1_pitch, 0) + self.add_rect(layer="metal1", offset=offset, - width=drc["minwidth_metal2"], + width=drc["minwidth_metal1"], height=self.logic_height) - self.rail_2_x_offsets.append(offset.x) + self.rail_2_x_offsets[self.rail_2_names[i]]=offset.x + 0.5*drc["minwidth_metal1"] # center offset + + self.rail_2_end_x = self.rail_2_start_x + (self.num_rails_2+1) * self.m2_pitch - def add_1st_row_routing(self): - # First rail routing left - left_side = [] - for pin in ["OE_bar","OE","CS","WE"]: - left_side.append(getattr(self,"msf_control_"+pin+"_position")) - line_indices = [1, 2, 3, 4] - for i in range(len(left_side)): - offset = left_side[i] - line_x_offset = self.rail_1_x_offsets[line_indices[i]] - self.add_rect(layer="metal1", - offset=offset, - width=line_x_offset - offset.x + drc["minwidth_metal2"], - height=drc["minwidth_metal1"]) - correct1 = vector(self.gap_between_rails, - self.via_shift) - correct2 = vector(self.contact_shift + drc["minwidth_metal2"],0) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=offset + correct1 - correct2, - rotate=90) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=vector(line_x_offset, offset[1]) + correct1, - rotate=90) - # First rail routing Right - right_side = [] - right_side.append(self.nand2_1_A_position) - right_side.append(self.nand2_1_B_position) - for size in ["1","2"]: - for pin in ["A","B","C"]: - right_side.append(getattr(self,"nand3_"+size+"_"+pin+"_position")) + def add_rblk_routing(self): + """ Connect the logic for the rblk generation """ + self.connect_rail_from_right(self.rblk_bar,"A","clk_bar") + self.connect_rail_from_right(self.rblk_bar,"B","oe") + self.connect_rail_from_right(self.rblk_bar,"C","cs") - line_indices = [2, 5, 5, 2, 3, 5, 4, 3] - for i in range(len(right_side)): - offset = right_side[i] - line_x_offset = self.rail_1_x_offsets[line_indices[i]] - base = vector(line_x_offset, offset[1]) - self.add_rect(layer="metal1", - offset=base, - width=offset.x - line_x_offset, - height=drc["minwidth_metal1"]) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=base + correct1, - rotate=90) + # Connect the NAND3 output to the inverter + # The pins are assumed to extend all the way to the cell edge + rblk_bar_pin = self.rblk_bar.get_pin("Z").ur() + inv_in_pin = self.rblk.get_pin("A").ll() + mid1 = vector(inv_in_pin.x,rblk_bar_pin.y) + self.add_path("metal1",[rblk_bar_pin,mid1,inv_in_pin]) - # OE_bar [Bus # 1] to nor2 B input - layer_stack = ("metal1", "via1", "metal2") - start = self.nor2_1_B_position - mid1 = vector(self.nor2_1_B_position.x+ 2 * drc["minwidth_metal2"], - start.y) - mid2 = vector(mid1.x, - self.nor2_1_gnd_position.y- 2 * drc["minwidth_metal1"]) - mid3 = vector(self.rail_1_x_offsets[1] + 0.5 * drc["minwidth_metal2"], - mid2.y) - end = [mid3.x, self.output_port_gap] - self.add_wire(layer_stack, [start, mid1, mid2, mid3, end]) + # Connect the output to the RBL + rblk_pin = self.rblk.get_pin("Z").rc() + rbl_in_pin = self.rbl.get_pin("en").lc() + mid1 = vector(rblk_pin.x,rbl_in_pin.y) + self.add_path("metal1",[rblk_pin,mid1,rbl_in_pin]) + + 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_position = vector(self.rail_1_x_offsets[rail], in_pin.y) + self.add_wire(("metal1","via1","metal2"),[in_pin, rail_position, rail_position - vector(0,self.m2_pitch)]) - layer_stack = ("metal1") - start = self.inv1_Z_position+ vector(0, + 0.5 * drc["minwidth_metal1"]) - mid1 = start + vector(drc["minwidth_metal2"], 0) - mid2 = vector(mid1.x, - self.nand2_1_B_position.y + 0.5 * drc["minwidth_metal1"]) - end = [self.nand2_1_B_position.x, mid2.y] - self.add_path(layer_stack, [start, mid1, mid2, end]) - - def add_2nd_row_routing(self): - # Second rail routing - left_side = [] - left_side.append(self.inv2_Z_position) - left_side.append(self.inv7_Z_position) - left_side.append(self.inv5_A_position) - line_indices = [1, 0, 2] - #line_indices = [1,2] - for i in range(len(left_side)): - offset = left_side[i] - line_x_offset = self.rail_2_x_offsets[line_indices[i]] - self.add_rect(layer="metal1", - offset=offset, - width=line_x_offset - offset.x+ drc["minwidth_metal2"], - height=drc["minwidth_metal1"]) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=[line_x_offset + self.gap_between_rails, - offset.y- self.via_shift], - rotate=90) - - # Replica bitline (rblk to replica bitline input) - layer_stack = ("metal1", "via1", "metal2") - start = vector(self.rail_2_x_offsets[1] + 0.5 * drc["minwidth_metal2"], - self.output_port_gap) - mid1 = vector(start.x, 0.5 * drc["minwidth_metal1"]) - end = vector(self.replica_en_offset.x, mid1.y) - - self.add_wire(layer_stack, [start, mid1, end]) - - height = self.replica_en_offset.y- end.y+ 0.5 * drc["minwidth_metal1"] - - self.add_rect(layer="metal1", - offset=end - vector([0.5 * drc["minwidth_metal1"]] * 2), - width=drc["minwidth_metal1"], - height=height) - - # Replica bitline (replica bitline output to the buffer [inv4,inv5]) - start = [self.rail_2_x_offsets[2], self.replica_out_offset[1]] - end = self.replica_out_offset - vector(0.5 * drc["minwidth_metal1"],0) - self.add_rect(layer="metal3", - offset=start, - width=self.replica_out_offset.x- self.rail_2_x_offsets[2], - height=drc["minwidth_metal3"]) - self.add_via(layers=("metal2", "via2", "metal3"), - offset=start) - self.add_via(layers=("metal2", "via2", "metal3"), - offset=end) - - def add_vdd_routing(self): - """ VDD routing between modules """ - vdd_rail_index = self.num_rails_2 - 1 - rail_2_x = self.rail_2_x_offsets[vdd_rail_index] - - # Connection between nor2 vdd to nand3 vdd - self.add_rect(layer="metal1", - offset=self.nor2_1_vdd_position, - width=rail_2_x + drc["minwidth_metal2"], - height=drc["minwidth_metal1"]) - - # Connection between top AND_Array vdd to the last line on rail2 - self.add_rect(layer="metal1", - offset=self.nand3_2_vdd_position, - width=(rail_2_x + drc["minwidth_metal2"] - - self.nand3_2_vdd_position.x), - height=drc["minwidth_metal1"]) - - # Connection in horizontal metal2 vdd rail - base = vector(rail_2_x + self.gap_between_rails, - - self.via_shift) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=base + self.nand2_1_vdd_position.scale(0, 1), + def connect_rail_from_right_m2m3(self,inst, pin, rail): + """ Helper routine to connect an unrotated/mirrored oriented instance to the rails """ + in_pin = inst.get_pin(pin).lc() + rail_position = vector(self.rail_1_x_offsets[rail], in_pin.y) + self.add_wire(("metal3","via2","metal2"),[in_pin, rail_position, rail_position - vector(0,self.m2_pitch)]) + # Bring it up to M2 for M2/M3 routing + self.add_via(layers=("metal1","via1","metal2"), + offset=in_pin + self.m1m2_via_offset, rotate=90) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=base + self.nand3_2_vdd_position.scale(0, 1), + 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_position = vector(self.rail_1_x_offsets[rail], in_pin.y) + self.add_wire(("metal1","via1","metal2"),[in_pin, rail_position, rail_position - vector(0,self.m2_pitch)]) + self.add_via(layers=("metal1","via1","metal2"), + offset=in_pin + self.m1m2_via_offset, rotate=90) - # Connection of msf_vdd to inv1 vdd - self.add_rect(layer="metal1", - offset=[self.msf_control_vdd_position.x, - self.inv1_vdd_position[1]], - width=drc["minwidth_metal1"], - height=self.msf_control_vdd_position.y- self.inv1_vdd_position[1]) - - # FIXME: added fudge to get to work. will fix with new pin structure. - vdd_offset = vector(self.replica_bitline.height+drc["minwidth_metal1"],3 * drc["minwidth_metal1"]) - - self.vdd1_position = vdd_offset + self.offset_replica_bitline - self.vdd2_position = vector(rail_2_x, self.output_port_gap) - - def add_gnd_routing(self): - """ GND routing """ - self.gnd_position = self.offset_replica_bitline - - # Connection of msf_control gnds to the metal2 gnd rail - for gnd_offset in self.msf_control_gnd_positions: - self.add_rect(layer="metal2", - offset=gnd_offset, - width=(self.rail_1_x_offsets[0] - gnd_offset.x - + drc["minwidth_metal2"]), - height=drc["minwidth_metal2"]) - - # Connect msf_control gnd to nand3 gnd - self.add_rect(layer="metal1", - offset=self.nor2_1_gnd_position, - width=self.offset_replica_bitline.x, - height=drc["minwidth_metal1"]) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=[self.rail_1_x_offsets[0] + self.gap_between_rails, - self.nor2_1_gnd_position.y- self.via_shift], + def connect_rail_from_left_m2m3(self,inst, pin, rail): + """ Helper routine to connect an unrotated/mirrored oriented instance to the rails """ + in_pin = inst.get_pin(pin).rc() + rail_position = vector(self.rail_1_x_offsets[rail], in_pin.y) + self.add_wire(("metal3","via2","metal2"),[in_pin, rail_position, rail_position - vector(0,self.m2_pitch)]) + self.add_via(layers=("metal1","via1","metal2"), + offset=in_pin + self.m1m2_via_offset, rotate=90) + self.add_via(layers=("metal2","via2","metal3"), + offset=in_pin + self.m2m3_via_offset, + rotate=90) + + + def add_wen_routing(self): + self.connect_rail_from_right(self.w_en_bar,"A","clk_bar") + self.connect_rail_from_right(self.w_en_bar,"B","we") + self.connect_rail_from_right(self.w_en_bar,"C","cs") - # nand3 gnd to replica bitline gnd - self.add_rect(layer="metal1", - offset=self.nand3_2_gnd_position, - width=self.offset_replica_bitline.x - self.nand3_2_gnd_position.x + drc["minwidth_metal1"], - height=drc["minwidth_metal1"]) + # Connect the NAND3 output to the inverter + # The pins are assumed to extend all the way to the cell edge + w_en_bar_pin = self.w_en_bar.get_pin("Z").ur() + inv_in_pin = self.pre_w_en.get_pin("A").ll() + mid1 = vector(inv_in_pin.x,w_en_bar_pin.y) + self.add_path("metal1",[w_en_bar_pin,mid1,inv_in_pin]) + - def add_input_routing(self): - """ Input pin routing """ - # WEb, CEb, OEb assign from msf_control pin - self.WEb_position = self.msf_control_WEb_position - self.CSb_position = self.msf_control_CSb_position - self.OEb_position = self.msf_control_OEb_position + + def add_trien_routing(self): + self.connect_rail_from_right(self.tri_en,"A","clk_buf") + self.connect_rail_from_right(self.tri_en,"B","oe_bar") - # Clk - clk_y = self.inv1_vdd_position.y+ 6 * drc["minwidth_metal1"] - self.clk_position = vector(0, clk_y) - - # clk port to inv1 A - layer_stack = ("metal1", "via1", "metal2") - start = self.inv1_A_position + vector(0, 0.5 * drc["minwidth_metal1"]) - mid1 = vector(self.inv1_A_position.x- 2 * drc["minwidth_metal2"], - start.y) - mid2 = vector(mid1.x, clk_y) - self.clk_position = vector(0, mid2[1]) - - self.add_wire(layer_stack, [start, mid1, mid2, self.clk_position]) + self.connect_rail_from_right_m2m3(self.tri_en_bar,"A","clk_bar") + self.connect_rail_from_right_m2m3(self.tri_en_bar,"B","oe") - # clk line to msf_control_clk - self.add_rect(layer="metal1", - offset=[self.msf_control_clk_position.x, - self.clk_position[1]], - width=drc["minwidth_metal1"], - height=(self.msf_control_clk_position.y - - self.clk_position[1])) + - # clk connection to nor2 A input - start = self.inv1_A_position + vector(- 2 * drc["minwidth_metal2"], - 0.5 * drc["minwidth_metal1"]) - mid1 = start - vector(3 * drc["minwidth_metal2"], 0) - mid2 = [mid1.x, self.nor2_1_A_position.y] + def add_sen_routing(self): + rbl_out_pin = self.rbl.get_pin("out").ul() + in_pin = self.pre_s_en_bar.get_pin("A").rc() + mid1 = vector(rbl_out_pin.x,in_pin.y) + self.add_path("metal1",[rbl_out_pin,mid1,in_pin]) + #s_en_pin = self.s_en.get_pin("Z").lc() - self.add_path("metal1", [start, mid1, mid2, self.nor2_1_A_position]) + + def add_clk_routing(self): + """ Route the clk and clk_bar signal internally """ - correct = vector(0, 0.5 * drc["minwidth_metal1"]) - - self.add_via(layers=("metal1", "via1", "metal2"), - offset=self.clk_position + correct, - rotate=270) - self.add_via(layers=("metal2", "via2", "metal3"), - offset=self.clk_position + correct, - rotate=270) + # clk_buf + clk_buf_pin = self.clk_buf.get_pin("Z").rc() + clk_buf_rail_position = vector(self.rail_1_x_offsets["clk_buf"], clk_buf_pin.y) + self.add_wire(("metal1","via1","metal2"),[clk_buf_pin, clk_buf_rail_position, clk_buf_rail_position - vector(0,self.m2_pitch)]) + + # clk_bar + self.connect_rail_from_left_m2m3(self.clk_bar,"Z","clk_bar") + + # clk_buf to msf control flops + msf_clk_pin = self.msf.get_pin("clk").bc() + mid1 = msf_clk_pin - vector(0,self.m2_pitch) + clk_buf_rail_position = vector(self.rail_1_x_offsets["clk_buf"], mid1.y) + # route on M2 to allow vdd connection + self.add_wire(("metal2","via1","metal1"),[msf_clk_pin, mid1, clk_buf_rail_position]) + def connect_pin_to_output_pin(self, inst, pin_name, out_name): + """ Create an output pin on the bottom side from the pin of a given instance. """ + out_pin = inst.get_pin(pin_name).center() + self.add_via(layers=("metal1","via1","metal2"), + offset=out_pin + self.m1m2_via_offset, + rotate=90) + self.add_layout_pin(text=out_name, + layer="metal2", + offset=out_pin.scale(1,0), + height=out_pin.y) + def add_output_routing(self): """ Output pin routing """ - # clk_bar - self.clk_bar_position = vector(self.rail_1_x_offsets[self.num_rails_1 - 1], - 0) - self.add_rect(layer="metal2", - offset=self.clk_bar_position, - width=drc["minwidth_metal2"], - height=self.output_port_gap) - self.add_via(layers=("metal2", "via2", "metal3"), - offset=self.clk_bar_position) + self.connect_pin_to_output_pin(self.tri_en, "Z", "tri_en") + self.connect_pin_to_output_pin(self.tri_en_bar, "Z", "tri_en_bar") + self.connect_pin_to_output_pin(self.w_en, "Z", "w_en") + self.connect_pin_to_output_pin(self.s_en, "Z", "s_en") + def add_supply_routing(self): - # tri_en - correct = vector (0, 0.5 * drc["minwidth_metal1"]) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=self.nor2_1_Z_position + correct, - rotate=270) - self.add_rect(layer="metal2", - offset=self.nor2_1_Z_position.scale(1, 0), - width=drc["minwidth_metal2"], - height=self.nor2_1_Z_position.y + correct.y) - self.add_via(layers=("metal2", "via2", "metal3"), - offset=self.nor2_1_Z_position.scale(1, 0)) - self.tri_en_position = vector(self.nor2_1_Z_position.x, 0) + rows_start = self.rail_1_start_x + self.overall_rail_1_gap + rows_end = max(self.row_1_width,self.row_2_width,self.row_3_width) + vdd_rail_position = vector(self.rail_1_x_offsets["vdd"], 0) + well_width = drc["minwidth_well"] + + # M1 gnd rail from inv1 to max + start_offset = self.clk_inv1.get_pin("gnd").lc() + row1_gnd_end_offset = vector(rows_end,start_offset.y) + self.add_path("metal1",[start_offset,row1_gnd_end_offset]) + rail_position = vector(self.rail_1_x_offsets["gnd"], start_offset.y) + self.add_wire(("metal1","via1","metal2"),[vector(rows_start,start_offset.y), rail_position, rail_position + vector(0,self.m2_pitch)]) - # tri_en_bar - correct = vector(drc["minwidth_metal2"], 0) - self.tri_en_bar_position = self.nand2_1_Z_position.scale(1, 0) - correct - self.add_via(layers=("metal1", "via1", "metal2"), - offset=self.nand2_1_Z_position - correct) - self.add_rect(layer="metal2", - offset=self.tri_en_bar_position, - width=drc["minwidth_metal2"], - height=self.nand2_1_Z_position.y+ drc["minwidth_metal1"]) - self.add_via(layers=("metal2", "via2", "metal3"), - offset=self.tri_en_bar_position) + # also add a well + around the rail + self.add_rect(layer="pwell", + offset=vector(rows_start,start_offset.y), + width=rows_end-rows_start, + height=well_width) + self.add_rect(layer="vtg", + offset=vector(rows_start,start_offset.y), + width=rows_end-rows_start, + height=well_width) - # w_en - self.w_en_position = vector(self.rail_2_x_offsets[0], 0) - self.add_rect(layer="metal2", - offset=self.w_en_position, - width=drc["minwidth_metal2"], - height=self.output_port_gap) - self.add_via(layers=("metal2", "via2", "metal3"), - offset=self.w_en_position) + # M1 vdd rail from inv1 to max + start_offset = self.clk_inv1.get_pin("vdd").lc() + row1_vdd_end_offset = vector(rows_end,start_offset.y) + self.add_path("metal1",[start_offset,row1_vdd_end_offset]) + rail_position = vector(self.rail_1_x_offsets["vdd"], start_offset.y) + self.add_wire(("metal1","via1","metal2"),[vector(rows_start,start_offset.y), rail_position, rail_position - vector(0,self.m2_pitch)]) + + # also add a well +- around the rail + self.add_rect(layer="nwell", + offset=vector(rows_start,start_offset.y)-vector(0,0.5*well_width), + width=rows_end-rows_start, + height=well_width) + self.add_rect(layer="vtg", + offset=vector(rows_start,start_offset.y)-vector(0,0.5*well_width), + width=rows_end-rows_start, + height=well_width) + + + # M1 gnd rail from inv1 to max + start_offset = vector(rows_start, self.tri_en.get_pin("gnd").lc().y) + row3_gnd_end_offset = vector(rows_end,start_offset.y) + self.add_path("metal1",[start_offset,row3_gnd_end_offset]) + rail_position = vector(self.rail_1_x_offsets["gnd"], start_offset.y) + self.add_wire(("metal1","via1","metal2"),[vector(rows_start,start_offset.y), rail_position, rail_position - vector(0,self.m2_pitch)]) + + # also add a well +- around the rail + self.add_rect(layer="pwell", + offset=vector(rows_start,start_offset.y)-vector(0,0.5*well_width), + width=rows_end-rows_start, + height=well_width) + self.add_rect(layer="vtg", + offset=vector(rows_start,start_offset.y)-vector(0,0.5*well_width), + width=rows_end-rows_start, + height=well_width) + + + # M1 vdd rail from inv1 to max + start_offset = vector(rows_start, self.w_en_bar.get_pin("vdd").lc().y) + row3_vdd_end_offset = vector(rows_end,start_offset.y) + self.add_path("metal1",[start_offset,row3_vdd_end_offset]) + rail_position = vector(self.rail_1_x_offsets["vdd"], start_offset.y) + self.add_wire(("metal1","via1","metal2"),[vector(rows_start,start_offset.y), rail_position, rail_position - vector(0,self.m2_pitch)]) + + + # Now connect the vdd and gnd rails between the replica bitline and the control logic + (rbl_row3_gnd,rbl_row1_gnd) = self.rbl.get_pin("gnd") + (rbl_row3_vdd,rbl_row1_vdd) = self.rbl.get_pin("vdd") + + self.add_path("metal1",[row1_gnd_end_offset,rbl_row1_gnd.lc()]) + self.add_path("metal1",[row1_vdd_end_offset,rbl_row1_vdd.lc()]) + self.add_path("metal1",[row3_gnd_end_offset,rbl_row3_gnd.lc()]) + # row 3 may have a jog due to unequal row heights, so force the full overlap at the end + self.add_path("metal1",[row3_vdd_end_offset - vector(self.m1_pitch,0),row3_vdd_end_offset,rbl_row3_vdd.lc()]) + + + # also add a well - around the rail + self.add_rect(layer="nwell", + offset=vector(rows_start,start_offset.y)-vector(0,well_width), + width=rows_end-rows_start, + height=well_width) + self.add_rect(layer="vtg", + offset=vector(rows_start,start_offset.y)-vector(0,well_width), + width=rows_end-rows_start, + height=well_width) - # s_en - self.s_en_position = self.inv4_Z_position.scale(1,0) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=self.inv4_Z_position) - self.add_rect(layer="metal2", - offset=self.s_en_position, - width=drc["minwidth_metal2"], - height=self.inv4_Z_position.y+ drc["minwidth_metal1"]) - self.add_via(layers=("metal2", "via2", "metal3"), - offset=self.s_en_position) diff --git a/compiler/delay_chain.py b/compiler/delay_chain.py new file mode 100644 index 00000000..da8df1f4 --- /dev/null +++ b/compiler/delay_chain.py @@ -0,0 +1,220 @@ +import debug +import design +from tech import drc +from pinv import pinv +from contact import contact +from vector import vector +from globals import OPTS + +class delay_chain(design.design): + """ + Generate a logic effort based delay chain. + Input is a list contains the electrical effort of each stage. + """ + + def __init__(self, fanout_list, name="delay_chain"): + """init function""" + design.design.__init__(self, name) + # FIXME: input should be logic effort value + # and there should be functions to get + # area efficient inverter stage list + + # number of inverters including any fanout loads. + self.fanout_list = fanout_list + self.num_inverters = 1 + sum(fanout_list) + self.num_top_half = round(self.num_inverters / 2.0) + + c = reload(__import__(OPTS.config.bitcell)) + self.mod_bitcell = getattr(c, OPTS.config.bitcell) + self.bitcell = self.mod_bitcell() + + self.add_pins() + self.create_module() + self.route_inv() + self.add_layout_pins() + self.DRC_LVS() + + def add_pins(self): + """ Add the pins of the delay chain""" + self.add_pin("in") + self.add_pin("out") + self.add_pin("vdd") + self.add_pin("gnd") + + def create_module(self): + """ Add the inverter logical module """ + + self.create_inv_list() + + self.inv = pinv(route_output=False) + self.add_mod(self.inv) + + # half chain length is the width of the layout + # invs are stacked into 2 levels so input/output are close + # extra metal is for the gnd connection U + self.width = self.num_top_half * self.inv.width + 2*drc["metal1_to_metal1"] + 0.5*drc["minwidth_metal1"] + self.height = 2 * self.inv.height + + self.add_inv_list() + + def create_inv_list(self): + """ + Generate a list of inverters. Each inverter has a stage + number and a flag indicating if it is a dummy load. This is + the order that they will get placed too. + """ + # First stage is always 0 and is not a dummy load + self.inv_list=[[0,False]] + for stage_num,fanout_size in zip(range(len(self.fanout_list)),self.fanout_list): + for i in range(fanout_size-1): + # Add the dummy loads + self.inv_list.append([stage_num+1, True]) + + # Add the gate to drive the next stage + self.inv_list.append([stage_num+1, False]) + + def add_inv_list(self): + """add the inverter and connect them based on the stage list """ + a_pin = self.inv.get_pin("A") + dummy_load_counter = 1 + self.inv_inst_list = [] + for i in range(self.num_inverters): + # First place the gates + if i < self.num_top_half: + # add top level that is upside down + inv_offset = vector(i * self.inv.width, 2 * self.inv.height) + inv_mirror="MX" + via_offset = inv_offset + a_pin.ll().scale(1,-1) + m1m2_via_rotate=270 + else: + # add bottom level from right to left + inv_offset = vector((self.num_inverters - i) * self.inv.width, 0) + inv_mirror="MY" + via_offset = inv_offset + a_pin.ll().scale(-1,1) + m1m2_via_rotate=90 + + self.add_via(layers=("metal1", "via1", "metal2"), + offset=via_offset, + rotate=m1m2_via_rotate) + cur_inv=self.add_inst(name="dinv{}".format(i), + mod=self.inv, + offset=inv_offset, + mirror=inv_mirror) + # keep track of the inverter instances so we can use them to get the pins + self.inv_inst_list.append(cur_inv) + + # Second connect them logically + cur_stage = self.inv_list[i][0] + next_stage = self.inv_list[i][0]+1 + if i == 0: + input = "in" + else: + input = "s{}".format(cur_stage) + if i == self.num_inverters-1: + output = "out" + else: + output = "s{}".format(next_stage) + + # if the gate is a dummy load don't connect the output + # else reset the counter + if self.inv_list[i][1]: + output = output+"n{0}".format(dummy_load_counter) + dummy_load_counter += 1 + else: + dummy_load_counter = 1 + + self.connect_inst(args=[input, output, "vdd", "gnd"]) + + + + def route_inv(self): + """ Add metal routing for each of the fanout stages """ + start_inv = end_inv = 0 + z_pin = self.inv.get_pin("Z") + a_pin = self.inv.get_pin("A") + for fanout in self.fanout_list: + # end inv number depends on the fan out number + end_inv = start_inv + fanout + start_inv_offset = self.inv_inst_list[start_inv].offset + end_inv_offset = self.inv_inst_list[end_inv].offset + if start_inv < self.num_top_half: + start_o_offset = start_inv_offset + z_pin.ll().scale(1,-1) + m1m2_via_rotate = 270 + y_dir = -1 + else: + start_o_offset = start_inv_offset + z_pin.ll().scale(-1,1) + m1m2_via_rotate = 90 + y_dir = 1 + + M2_start = start_o_offset + vector(0,drc["minwidth_metal2"]).scale(1,y_dir*0.5) + + self.add_via(layers=("metal1", "via1", "metal2"), + offset=start_o_offset, + rotate=m1m2_via_rotate) + + if end_inv < self.num_top_half: + end_i_offset = end_inv_offset + a_pin.ll().scale(1,-1) + M2_end = end_i_offset - vector(0, 0.5 * drc["minwidth_metal2"]) + else: + end_i_offset = end_inv_offset + a_pin.ll().scale(-1,1) + M2_end = end_i_offset + vector(0, 0.5 * drc["minwidth_metal2"]) + + # We need a wire if the routing spans multiple rows + if start_inv < self.num_top_half and end_inv >= self.num_top_half: + mid = vector(self.num_top_half * self.inv.width - 0.5 * drc["minwidth_metal2"], + M2_start[1]) + self.add_wire(("metal2", "via2", "metal3"), + [M2_start, mid, M2_end]) + else: + self.add_path(("metal2"), [M2_start, M2_end]) + # set the start of next one after current end + start_inv = end_inv + + def add_layout_pins(self): + """ Add vdd and gnd rails and the input/output. Connect the gnd rails internally on + the top end with no input/output to obstruct. """ + vdd_pin = self.inv.get_pin("vdd") + gnd_pin = self.inv.get_pin("gnd") + for i in range(3): + (offset,y_dir)=self.get_gate_offset(0, self.inv.height, i) + rail_width = self.num_top_half * self.inv.width + if i % 2: + self.add_layout_pin(text="vdd", + layer="metal1", + offset=offset + vdd_pin.ll().scale(1,y_dir), + width=rail_width, + height=drc["minwidth_metal1"]) + else: + self.add_layout_pin(text="gnd", + layer="metal1", + offset=offset + gnd_pin.ll().scale(1,y_dir), + width=rail_width, + height=drc["minwidth_metal1"]) + + # Use the right most parts of the gnd rails and add a U connector + # We still have the two gnd pins, but it is an either-or connect + gnd_pins = self.get_pin("gnd") + gnd_start = gnd_pins[0].rc() + gnd_mid1 = gnd_start + vector(2*drc["metal1_to_metal1"],0) + gnd_end = gnd_pins[1].rc() + gnd_mid2 = gnd_end + vector(2*drc["metal1_to_metal1"],0) + #self.add_wire(("metal1","via1","metal2"), [gnd_start, gnd_mid1, gnd_mid2, gnd_end]) + self.add_path("metal1", [gnd_start, gnd_mid1, gnd_mid2, gnd_end]) + + # input is A pin of first inverter + a_pin = self.inv.get_pin("A") + first_offset = self.inv_inst_list[0].offset + self.add_layout_pin(text="in", + layer="metal1", + offset=first_offset+a_pin.ll().scale(1,-1) - vector(0,drc["minwidth_metal1"])) + + + + # output is Z pin of last inverter + z_pin = self.inv.get_pin("Z") + self.add_layout_pin(text="out", + layer="metal1", + offset=z_pin.ll().scale(0,1), + width=self.inv.width-z_pin.lx()) + + diff --git a/compiler/example_config_freepdk45.py b/compiler/example_config_freepdk45.py index bb739621..fd171460 100644 --- a/compiler/example_config_freepdk45.py +++ b/compiler/example_config_freepdk45.py @@ -21,6 +21,7 @@ write_driver_array = "write_driver_array" tri_gate = "tri_gate" tri_gate_array = "tri_gate_array" wordline_driver = "wordline_driver" +replica_bitline = "replica_bitline" replica_bitcell = "replica_bitcell" bitcell = "bitcell" -delay_chain = "logic_effort_dc" +delay_chain = "delay_chain" diff --git a/compiler/example_config_scn3me_subm.py b/compiler/example_config_scn3me_subm.py index 478b4525..69922ef5 100644 --- a/compiler/example_config_scn3me_subm.py +++ b/compiler/example_config_scn3me_subm.py @@ -21,6 +21,7 @@ write_driver_array = "write_driver_array" tri_gate = "tri_gate" tri_gate_array = "tri_gate_array" wordline_driver = "wordline_driver" +replica_bitline = "replica_bitline" replica_bitcell = "replica_bitcell" bitcell = "bitcell" -delay_chain = "logic_effort_dc" +delay_chain = "delay_chain" diff --git a/compiler/gdsMill/gdsMill/vlsiLayout.py b/compiler/gdsMill/gdsMill/vlsiLayout.py index d4075378..0c047c0e 100644 --- a/compiler/gdsMill/gdsMill/vlsiLayout.py +++ b/compiler/gdsMill/gdsMill/vlsiLayout.py @@ -651,8 +651,9 @@ class VlsiLayout: def getLabelDBInfo(self,label_name): """ - Return the coordinates in DB units and layer of a label + Return the coordinates in DB units and layer of all matching labels """ + label_list = [] label_layer = None label_coordinate = [None, None] @@ -661,18 +662,24 @@ class VlsiLayout: if Text.textString == label_name or Text.textString == label_name+"\x00": label_layer = Text.drawingLayer label_coordinate = Text.coordinates[0] + if label_layer!=None: + label_list.append((label_coordinate,label_layer)) - debug.check(label_layer!=None,"Did not find label {0}.".format(label_name)) - return (label_coordinate, label_layer) + debug.check(len(label_list)>0,"Did not find labels {0}.".format(label_name)) + return label_list def getLabelInfo(self,label_name): """ Return the coordinates in USER units and layer of a label """ - (label_coordinate,label_layer)=self.getLabelDBInfo(label_name) - user_coordinates = [x*self.units[0] for x in label_coordinate] - return (user_coordinates,label_layer) + label_list=self.getLabelDBInfo(label_name) + new_list=[] + for label in label_list: + (label_coordinate,label_layer)=label + user_coordinates = [x*self.units[0] for x in label_coordinate] + new_list.append(user_coordinates,label_layer) + return new_list def getPinShapeByLocLayer(self, coordinate, layer): """ @@ -729,16 +736,25 @@ class VlsiLayout: Search for a pin label and return the largest enclosing rectangle on the same layer as the pin label. """ - (label_coordinate,label_layer)=self.getLabelDBInfo(label_name) - return self.getPinShapeByDBLocLayer(label_coordinate, label_layer) + label_list=self.getLabelDBInfo(label_name) + shape_list=[] + for label in label_list: + (label_coordinate,label_layer)=label + shape_list.append(self.getPinShapeByDBLocLayer(label_coordinate, label_layer)) + return shape_list def getAllPinShapesByLabel(self,label_name): """ Search for a pin label and return ALL the enclosing rectangles on the same layer as the pin label. """ - (label_coordinate,label_layer)=self.getLabelDBInfo(label_name) - return self.getAllPinShapesByDBLocLayer(label_coordinate, label_layer) + + label_list=self.getLabelDBInfo(label_name) + shape_list=[] + for label in label_list: + (label_coordinate,label_layer)=label + shape_list.append(self.getAllPinShapesByDBLocLayer(label_coordinate, label_layer)) + return shape_list def getAllPinShapesInStructureList(self,coordinates,layer): """ diff --git a/compiler/geometry.py b/compiler/geometry.py index 079ac64e..80b38543 100644 --- a/compiler/geometry.py +++ b/compiler/geometry.py @@ -32,13 +32,18 @@ class instance(geometry): def __init__(self, name, mod, offset, mirror, rotate): """Initializes an instance to represent a module""" geometry.__init__(self) + debug.check(mirror not in ["R90","R180","R270"], "Please use rotation and not mirroring during instantiation.") + self.name = name self.mod = mod self.gds = mod.gds self.rotate = rotate self.offset = vector(offset).snap_to_grid() self.mirror = mirror - + + self.boundary = [vector(0,0),vector(mod.width,mod.height)] + self.transform(offset,mirror,rotate) + debug.info(3, "creating instance: " + self.name) def gds_write_file(self, newLayout): @@ -53,6 +58,95 @@ class instance(geometry): mirror=self.mirror, rotate=self.rotate) + def normalize(self): + """ Re-find the LL and UR points after a transform """ + (first,second)=self.boundary + ll = vector(min(first[0],second[0]),min(first[1],second[1])) + ur = vector(max(first[0],second[0]),max(first[1],second[1])) + self.boundary=[ll,ur] + + def transform(self,offset,mirror,rotate): + """ Transform with offset, mirror and rotation to get the absolute pin location. + We must then re-find the ll and ur. The master is the cell instance. """ + (ll,ur) = self.boundary + if mirror=="MX": + ll=ll.scale(1,-1) + ur=ur.scale(1,-1) + elif mirror=="MY": + ll=ll.scale(-1,1) + ur=ur.scale(-1,1) + elif mirror=="XY": + ll=ll.scale(-1,-1) + ur=ur.scale(-1,-1) + + if rotate==90: + ll=ll.rotate_scale(-1,1) + ur=ur.rotate_scale(-1,1) + elif rotate==180: + ll=ll.scale(-1,-1) + ur=ur.scale(-1,-1) + elif rotate==270: + ll=ll.rotate_scale(1,-1) + ur=ur.rotate_scale(1,-1) + + self.boundary=[offset+ll,offset+ur] + self.normalize() + + def ll(self): + """ Return the lower left corner """ + return self.boundary[0] + + def ur(self): + """ Return the upper right corner """ + return self.boundary[1] + + def lr(self): + """ Return the lower right corner """ + return vector(self.boundary[1].x, self.boundary[0].y) + + def ul(self): + """ Return the upper left corner """ + return vector(self.boundary[0].x, self.boundary[1].y) + + + def uy(self): + """ Return the upper edge """ + return self.boundary[1].y + + def by(self): + """ Return the bottom edge """ + return self.boundary[0].y + + def lx(self): + """ Return the left edge """ + return self.boundary[0].x + + def rx(self): + """ Return the right edge """ + return self.boundary[1].x + + def get_pin(self,name): + """ Return an absolute pin that is offset and transformed based on + this instance location. """ + + import copy + pin = copy.deepcopy(self.mod.get_pin(name)) + pin.transform(self.offset,self.mirror,self.rotate) + return pin + + def get_pins(self,name): + """ Return an absolute pin that is offset and transformed based on + this instance location. """ + + import copy + pin = copy.deepcopy(self.mod.get_pins(name)) + + new_pins = [] + for p in pin: + p.transform(self.offset,self.mirror,self.rotate) + new_pins.append(p) + return new_pins + def __str__(self): """ override print function output """ return "inst: " + self.name + " mod=" + self.mod.name diff --git a/compiler/hierarchical_decoder.py b/compiler/hierarchical_decoder.py index bfd03411..4de7e9cf 100644 --- a/compiler/hierarchical_decoder.py +++ b/compiler/hierarchical_decoder.py @@ -18,76 +18,60 @@ class hierarchical_decoder(design.design): Dynamically generated hierarchical decoder. """ - def __init__(self, nand2_nmos_width, nand3_nmos_width, rows): + def __init__(self, rows): design.design.__init__(self, "hierarchical_decoder_{0}rows".format(rows)) c = reload(__import__(OPTS.config.bitcell)) self.mod_bitcell = getattr(c, OPTS.config.bitcell) - self.bitcell_height = self.mod_bitcell.chars["height"] + self.bitcell_height = self.mod_bitcell.height + + self.pre2x4_inst = [] + self.pre3x8_inst = [] self.rows = rows - self.nand2_nmos_width = nand2_nmos_width - self.nand3_nmos_width = nand3_nmos_width self.num_inputs = int(math.log(self.rows, 2)) self.create_layout() self.DRC_LVS() def create_layout(self): self.add_modules() - self.setup_layout_offsets() self.setup_layout_constants() - self.create_decoder() - # We only need to call the offset_all_coordinate function when there - # are vertical metal rails. - if (self.num_inputs >= 4): - self.offset_all_coordinates() - - def create_decoder(self): self.add_pins() - self.dimensions_hierarchy_decoder() self.create_pre_decoder() self.create_row_decoder() self.create_vertical_rail() + self.route_vdd_gnd() + # We only need to call the offset_all_coordinate function when there + # are vertical metal rails. + #if (self.num_inputs >= 4): + # self.offset_all_coordinates() def add_modules(self): self.m1m2_via = contact(layer_stack=("metal1", "via1", "metal2")) # Vertical metal rail gap definition self.metal2_extend_contact = (self.m1m2_via.second_layer_height - self.m1m2_via.contact_width) / 2 - self.gap_between_rails = self.metal2_extend_contact + drc["metal2_to_metal2"] - self.gap_between_rail_offset = self.gap_between_rails + drc["minwidth_metal2"] + self.metal2_spacing = self.metal2_extend_contact + drc["metal2_to_metal2"] + self.metal2_pitch = self.metal2_spacing + drc["minwidth_metal2"] self.via_shift = (self.m1m2_via.second_layer_width - self.m1m2_via.first_layer_width) / 2 # used to shift contact when connecting to NAND3 C pin down self.contact_shift = (self.m1m2_via.first_layer_width - self.m1m2_via.contact_width) / 2 - self.inv = pinv(nmos_width=drc["minwidth_tx"], - beta=2, - height=self.bitcell_height) + self.inv = pinv() self.add_mod(self.inv) - self.nand2 = nand_2(nmos_width=self.nand2_nmos_width, - height=self.bitcell_height) + self.nand2 = nand_2() self.add_mod(self.nand2) - self.nand3 = nand_3(nmos_width=self.nand3_nmos_width, - height=self.bitcell_height) + self.nand3 = nand_3() self.add_mod(self.nand3) # CREATION OF PRE-DECODER - self.pre2_4 = pre2x4(self.nand2_nmos_width, "pre2x4") + self.pre2_4 = pre2x4() self.add_mod(self.pre2_4) - self.pre3_8 = pre3x8(self.nand3_nmos_width, "pre3x8") + self.pre3_8 = pre3x8() self.add_mod(self.pre3_8) - def setup_layout_offsets(self): - self.vdd_positions = [] - self.gnd_positions = [] - self.decode_out_positions = [] - self.A_positions = [] - - self.pre_decoder_vdd_positions = [] - self.pre_decoder_gnd_positions = [] - def determine_predecodes(self,num_inputs): - # Determines the number of 2:4 pre-decoder and 3:8 pre-decoder needed - # based on the number of inputs + """Determines the number of 2:4 pre-decoder and 3:8 pre-decoder + needed based on the number of inputs""" if (num_inputs == 2): return (1,0) elif (num_inputs == 3): @@ -112,489 +96,444 @@ class hierarchical_decoder(design.design): self.no_of_pre2x4=p2x4 self.no_of_pre3x8=p3x8 - # Stromg the index of the vertical rails in different groups. These - # vertical lines is used to connect pre-decoder to row-decoder - self.predecoder_output_groups = [] # This array is a 2D array. - self.group_sizes = [] + self.predec_groups = [] # This array is a 2D array. # Distributing vertical rails to different groups. One group belongs to one pre-decoder. # For example, for two 2:4 pre-decoder and one 3:8 pre-decoder, we will # have total 16 output lines out of these 3 pre-decoders and they will # be distributed as [ [0,1,2,3] ,[4,5,6,7], [8,9,10,11,12,13,14,15] ] - # in self.predecoder_output_groups + # in self.predec_groups index = 0 for i in range(self.no_of_pre2x4): lines = [] for j in range(4): lines.append(index) index = index + 1 - self.predecoder_output_groups.append(lines) - self.group_sizes.append(4) + self.predec_groups.append(lines) for i in range(self.no_of_pre3x8): lines = [] for j in range(8): lines.append(index) index = index + 1 - self.predecoder_output_groups.append(lines) - self.group_sizes.append(8) + self.predec_groups.append(lines) + self.calculate_dimensions() + + def add_pins(self): + """ Add the module pins """ + for i in range(self.num_inputs): self.add_pin("A[{0}]".format(i)) - if(self.num_inputs >= 4): - for j in range(self.rows): - self.add_pin("decode_out[{0}]".format(j)) - else: - for j in range(self.rows): - self.add_pin("out[{0}]".format(j)) + for j in range(self.rows): + self.add_pin("decode[{0}]".format(j)) self.add_pin("vdd") self.add_pin("gnd") - def dimensions_hierarchy_decoder(self): - self.total_number_of_predecoder_outputs = (4 * self.no_of_pre2x4 - + 8 * self.no_of_pre3x8) + def calculate_dimensions(self): + """ Calculate the overal dimensions of the hierarchical decoder """ + + # If we have 4 or fewer rows, the predecoder is the decoder itself + if self.num_inputs>=4: + self.total_number_of_predecoder_outputs = 4*self.no_of_pre2x4 + 8*self.no_of_pre3x8 + else: + self.total_number_of_predecoder_outputs = 0 # Calculates height and width of pre-decoder, if(self.no_of_pre3x8 > 0): self.predecoder_width = self.pre3_8.width else: self.predecoder_width = self.pre2_4.width - self.predecoder_height = (self.pre2_4.height * self.no_of_pre2x4 - + self.pre3_8.height * self.no_of_pre3x8) + self.predecoder_height = self.pre2_4.height*self.no_of_pre2x4 + self.pre3_8.height*self.no_of_pre3x8 # Calculates height and width of row-decoder if (self.num_inputs == 4 or self.num_inputs == 5): nand_width = self.nand2.width else: nand_width = self.nand3.width - total_gap = (self.gap_between_rail_offset - * self.total_number_of_predecoder_outputs) - self.row_decoder_width = (nand_width + total_gap - + self.inv.width) + self.routing_width = self.metal2_pitch*self.total_number_of_predecoder_outputs + self.row_decoder_width = nand_width + self.routing_width + self.inv.width self.row_decoder_height = self.inv.height * self.rows # Calculates height and width of hierarchical decoder - self.height = (self.predecoder_height - + self.row_decoder_height) - self.width = self.predecoder_width + total_gap + self.height = self.predecoder_height + self.row_decoder_height + self.width = self.predecoder_width + self.routing_width def create_pre_decoder(self): """ Creates pre-decoder and places labels input address [A] """ + for i in range(self.no_of_pre2x4): self.add_pre2x4(i) - self.add_lables_pre2x4(i) - for j in range(self.no_of_pre3x8): - pre3x8_yoffset=self.add_pre3x8(j) - self.add_lables_pre3x8(j,pre3x8_yoffset) + + for i in range(self.no_of_pre3x8): + self.add_pre3x8(i) - def add_pre2x4(self,i): + def add_pre2x4(self,num): + """ Add a 2x4 predecoder """ + if (self.num_inputs == 2): - base = vector(0,0) - mod_dir = vector(1,1) + base = vector(self.routing_width,0) mirror = "RO" index_off1 = index_off2 = 0 else: - base= vector(self.pre2_4.width, i * self.pre2_4.height) - mod_dir = vector(-1,1) + base= vector(self.routing_width+self.pre2_4.width, num * self.pre2_4.height) mirror = "MY" - index_off1 = i * 2 - index_off2 = i * 4 + index_off1 = num * 2 + index_off2 = num * 4 pins = [] for input_index in range(2): pins.append("A[{0}]".format(input_index + index_off1)) for output_index in range(4): pins.append("out[{0}]".format(output_index + index_off2)) - pins = pins + ["vdd", "gnd"] + pins.extend(["vdd", "gnd"]) - self.add_inst(name="pre[{0}]".format(i), - mod=self.pre2_4, - offset=base, - mirror=mirror) + self.pre2x4_inst.append(self.add_inst(name="pre[{0}]".format(num), + mod=self.pre2_4, + offset=base, + mirror=mirror)) self.connect_inst(pins) - vdd_offset = base + self.pre2_4.vdd_position.scale(mod_dir) - self.pre_decoder_vdd_positions.append(vdd_offset) - self.add_label(text="vdd", - layer="metal1", - offset=vdd_offset) + self.add_pre2x4_pins(num) - gnd_offset = base + self.pre2_4.gnd_position.scale(mod_dir) - self.pre_decoder_gnd_positions.append(gnd_offset) - self.add_label(text="gnd", - layer="metal1", - offset=gnd_offset) + - def add_lables_pre2x4(self,i): - pre2_4_base = i * self.pre2_4.height - # ADDING LABELS FOR INPUT SIDE OF THE 2:4 PRE-DECODER - if (self.num_inputs == 2): - xoffset = self.pre2_4.x_off_inv_1 - else: - xoffset = self.pre2_4.width - self.pre2_4.x_off_inv_1 - for inv_2x4 in range(2): - if (inv_2x4 % 2 == 0): - pin_y = self.inv.A_position.y - else: - pin_y = (self.inv.height - self.inv.A_position.y - - drc["metal1_to_metal1"]) - yoffset = pre2_4_base + inv_2x4 * self.inv.height + pin_y - self.add_label(text="A[{0}]".format(inv_2x4 + i * 2), - layer="metal1", - offset=[xoffset, yoffset]) - self.A_positions.append(vector(xoffset, yoffset)) + def add_pre2x4_pins(self,num): + """ Add the input pins to the 2x4 predecoder """ - # ADDING LABELS FOR OUTPUT SIDE OF THE 2:4 PRE-DECODER - for inv_2x4 in range(4): - if (self.num_inputs == 2): - xoffset = self.pre2_4.x_off_inv_2 + self.inv.Z_position.x - else: - xoffset = 0 - if (inv_2x4 % 2 == 0): - pin_y = self.inv.Z_position.y - else: - pin_y = self.inv.height - self.inv.Z_position.y - yoffset = pre2_4_base + inv_2x4 * self.inv.height + pin_y - self.add_label(text="out[{0}]".format(inv_2x4 + i * 4), - layer="metal1", - offset=[xoffset, yoffset]) + for i in range(2): + pin = self.pre2x4_inst[num].get_pin("in[{}]".format(i)) + pin_offset = pin.ll() + + pin = self.pre2_4.get_pin("in[{}]".format(i)) + self.add_layout_pin(text="A[{0}]".format(i + 2*num ), + layer="metal2", + offset=pin_offset, + width=pin.width(), + height=pin.height()) - def add_pre3x8(self,j): + + def add_pre3x8(self,num): + """ Add 3x8 numbered predecoder """ if (self.num_inputs == 3): - offset = vector(0,0) + offset = vector(self.routing_width,0) mirror ="R0" - mod_dir = vector(1,1) - index_off1 = index_off2 = 0 else: - offset = vector(self.pre3_8.width, - self.no_of_pre2x4 * self.pre2_4.height - + j * self.pre3_8.height) + height = self.no_of_pre2x4*self.pre2_4.height + num*self.pre3_8.height + offset = vector(self.routing_width+self.pre3_8.width, height) mirror="MY" - mod_dir = vector(-1,1) - index_off1 = j * 3 + self.no_of_pre2x4 * 2 - index_off2 = j * 8 + self.no_of_pre2x4 * 4 + + # If we had 2x4 predecodes, those are used as the lower + # decode output bits + in_index_offset = num * 3 + self.no_of_pre2x4 * 2 + out_index_offset = num * 8 + self.no_of_pre2x4 * 4 pins = [] for input_index in range(3): - pins.append("A[{0}]".format(input_index + index_off1)) + pins.append("A[{0}]".format(input_index + in_index_offset)) for output_index in range(8): - pins.append("out[{0}]".format(output_index + index_off2)) - pins = pins + ["vdd", "gnd"] + pins.append("out[{0}]".format(output_index + out_index_offset)) + pins.extend(["vdd", "gnd"]) - self.add_inst(name="pre3x8[{0}]".format(j), - mod=self.pre3_8, - offset=offset, - mirror=mirror) + self.pre3x8_inst.append(self.add_inst(name="pre3x8[{0}]".format(num), + mod=self.pre3_8, + offset=offset, + mirror=mirror)) self.connect_inst(pins) - vdd_offset = offset + self.pre3_8.vdd_position.scale(mod_dir) - self.pre_decoder_vdd_positions.append(vdd_offset) - self.add_label(text="vdd", - layer="metal1", - offset=vdd_offset) + # The 3x8 predecoders will be stacked, so use yoffset + self.add_pre3x8_pins(num,offset) - gnd_offset = offset + self.pre3_8.gnd_position.scale(mod_dir) - self.pre_decoder_gnd_positions.append(gnd_offset) - self.add_label(text="gnd", - layer="metal1", - offset=gnd_offset) - return offset.y + def add_pre3x8_pins(self,num,offset): + """ Add the input pins to the 3x8 predecoder at the given offset """ - def add_lables_pre3x8(self,j,pre3x8_yoffset): - # ADDING LABELS FOR INPUT SIDE OF THE 3:8 PRE-DECODER - if (self.num_inputs == 3): - xoffset = self.pre3_8.x_off_inv_1 - else: - xoffset = self.pre3_8.width - self.pre3_8.x_off_inv_1 - for inv_3x8 in range(3): - if (inv_3x8 % 2 == 0): - pin_y = self.inv.A_position.y - else: - pin_y = (self.inv.height - self.inv.Z_position.y - -drc["minwidth_metal1"]) - yoffset = pre3x8_yoffset + inv_3x8 * (self.inv.height) + pin_y - A_index = self.no_of_pre2x4 * 2 + inv_3x8 + j * 3 - self.add_label(text="A[{0}]".format(A_index), - layer="metal1", - offset=[xoffset, yoffset]) - self.A_positions.append(vector(xoffset, yoffset)) + for i in range(3): + pin = self.pre3x8_inst[num].get_pin("in[{}]".format(i)) + pin_offset = pin.ll() + self.add_layout_pin(text="A[{0}]".format(i + 3*num + 2*self.no_of_pre2x4), + layer="metal2", + offset=pin_offset, + width=pin.width(), + height=pin.height()) - # ADDING LABELS FOR OUTPUT SIDE OF THE 3:8 PRE-DECODER - for inv_3x8 in range(8): - if (self.num_inputs == 3): - xoffset = self.pre3_8.x_off_inv_2 + self.inv.Z_position.x - else: - xoffset = 0 - if (inv_3x8 % 2 == 0): - pin_y = self.inv.Z_position.y - else: - pin_y = self.inv.height - self.inv.Z_position.y - yoffset = pre3x8_yoffset + inv_3x8 * self.inv.height + pin_y - out_index = self.no_of_pre2x4 * 4 + inv_3x8 + j * 8 - self.add_label(text="out[{0}]".format(out_index), - layer="metal1", - offset=[xoffset, yoffset]) def create_row_decoder(self): - # Create the row-decoder using NAND2/NAND3 and Inverter and places the - # output labels [out/decode_out] + """ Create the row-decoder by placing NAND2/NAND3 and Inverters + and add the primary decoder output pins. """ if (self.num_inputs >= 4): - self.add_decoder_nand_array_and_labels() - self.add_decoder_inv_array_and_labels() + self.add_decoder_nand_array() + self.add_decoder_inv_array_and_pins() - def add_decoder_nand_array_and_labels(self): + def add_decoder_nand_array(self): + """ Add a column of NAND gates for final decode """ + # Row Decoder NAND GATE array for address inputs <5. if (self.num_inputs == 4 or self.num_inputs == 5): - nand = self.nand2 - correct = 0 - nand_name ="NAND2" - self.add_nand_array(nand,correct,nand_name) + self.add_nand_array(nand_mod=self.nand2) # FIXME: Can we convert this to the connect_inst with checks? - for i in range(self.group_sizes[0]): - for j in range(self.group_sizes[1]): + for i in range(len(self.predec_groups[0])): + for j in range(len(self.predec_groups[1])): pins =["out[{0}]".format(i), - "out[{0}]".format(j + self.group_sizes[0]), - "Z[{0}]".format(self.group_sizes[1] * i + j), + "out[{0}]".format(j + len(self.predec_groups[0])), + "Z[{0}]".format(len(self.predec_groups[1])*i + j), "vdd", "gnd"] self.connect_inst(args=pins, check=False) # Row Decoder NAND GATE array for address inputs >5. elif (self.num_inputs > 5): - nand = self.nand3 - correct = drc["minwidth_metal1"] - nand_name ="NAND3" - self.add_nand_array(nand,correct,nand_name) + self.add_nand_array(nand_mod=self.nand3, + correct=drc["minwidth_metal1"]) # This will not check that the inst connections match. - for i in range(self.group_sizes[0]): - for j in range(self.group_sizes[1]): - for k in range(self.group_sizes[2]): - Z_index = (self.group_sizes[1] * self.group_sizes[2]* i - + self.group_sizes[2] * j + k) + for i in range(len(self.predec_groups[0])): + for j in range(len(self.predec_groups[1])): + for k in range(len(self.predec_groups[2])): + Z_index = len(self.predec_groups[1])*len(self.predec_groups[2]) * i \ + + len(self.predec_groups[2])*j + k pins = ["out[{0}]".format(i), - "out[{0}]".format(j + self.group_sizes[0]), - "out[{0}]".format(k + self.group_sizes[0] + self.group_sizes[1]), + "out[{0}]".format(j + len(self.predec_groups[0])), + "out[{0}]".format(k + len(self.predec_groups[0]) + len(self.predec_groups[1])), "Z[{0}]".format(Z_index), "vdd", "gnd"] self.connect_inst(args=pins, check=False) - def add_nand_array(self,nand,correct,nand_name): + def add_nand_array(self, nand_mod, correct=0): + """ Add a column of NAND gates for the decoder above the predecoders.""" + + z_pin = nand_mod.get_pin("Z") + a_pin = self.inv.get_pin("A") + rect_height = z_pin.uy()-a_pin.by() + for row in range(self.rows): - name = nand_name+"_[{0}]".format(row) + name = "DEC_NAND[{0}]".format(row) if ((row % 2) == 0): - y_off = self.predecoder_height + (nand.height) * (row) + y_off = self.predecoder_height + nand_mod.height*row y_dir = 1 mirror = "R0" + rect_offset = vector(self.routing_width + nand_mod.width, y_off + z_pin.uy() - rect_height) + else: - y_off = self.predecoder_height + (nand.height) * (row + 1) - y_dir = - 1 + y_off = self.predecoder_height + nand_mod.height*(row + 1) + y_dir = -1 mirror = "MX" + rect_offset =vector(self.routing_width + nand_mod.width, y_off - z_pin.uy()) self.add_inst(name=name, - mod=nand, - offset=[0, y_off], + mod=nand_mod, + offset=[self.routing_width, y_off], mirror=mirror) - self.add_rect(layer="metal1", - offset=[nand.width - correct, - y_off + y_dir * (nand.Z_position.y-correct)], - width=drc["minwidth_metal1"], - height=y_dir * drc["minwidth_metal1"]) - def add_decoder_inv_array_and_labels(self): - # Row Decoder INVERTER array insts. + self.add_rect(layer="metal1", + offset=rect_offset, + width=drc["minwidth_metal1"], + height=rect_height) + + + def add_decoder_inv_array_and_pins(self): + """Add a column of INV gates for the decoder above the predecoders + and to the right of the NAND decoders.""" + + z_pin = self.inv.get_pin("Z") + if (self.num_inputs == 4 or self.num_inputs == 5): - x_off = self.nand2.width + x_off = self.routing_width + self.nand2.width else: - x_off = self.nand3.width + x_off = self.routing_width + self.nand3.width + for row in range(self.rows): - name = "INVERTER_[{0}]".format(row) - if ((row % 2) == 0): + name = "DEC_INV_[{0}]".format(row) + if (row % 2 == 0): inv_row_height = self.inv.height * row mirror = "R0" + y_dir = 1 else: inv_row_height = self.inv.height * (row + 1) mirror = "MX" + y_dir = -1 y_off = self.predecoder_height + inv_row_height - + offset = vector(x_off,y_off) + self.add_inst(name=name, mod=self.inv, - offset=[x_off, y_off], + offset=offset, mirror=mirror) # This will not check that the inst connections match. self.connect_inst(args=["Z[{0}]".format(row), - "decode_out[{0}]".format(row), + "decode[{0}]".format(row), "vdd", "gnd"], check=False) - # add vdd and gnd label - for row in range(self.rows): - if ((row % 2) == 0): - offset = vector(0, self.predecoder_height + row*(self.inv.height)) - vdd_offset = offset + self.inv.vdd_position.scale(0,1) - gnd_offset = offset + self.inv.gnd_position.scale(0,1) - else: - offset = vector(0, self.predecoder_height + (row+1)*(self.inv.height)) - vdd_offset = offset + self.inv.vdd_position.scale(0, -1) - gnd_offset = offset + self.inv.gnd_position.scale(0, -1) - self.vdd_positions.append(vdd_offset) - self.add_label(text="vdd", - layer="metal1", - offset=vdd_offset) - self.gnd_positions.append(gnd_offset) - self.add_label(text="gnd", - layer="metal1", - offset=gnd_offset) + self.add_layout_pin(text="decode[{0}]".format(row), + layer="metal1", + offset=offset+z_pin.ll().scale(1,y_dir), + width=z_pin.width(), + height=y_dir*z_pin.height()) - # add output label for Row Decoder INVERTER array. - if (self.num_inputs == 4 or self.num_inputs == 5): - x_off = self.nand2.width + self.inv.Z_position.x - else: - x_off = self.nand3.width + self.inv.Z_position.x - for row in range(self.rows): - if ((row % 2) == 0): - pin_y = row * self.inv.height + self.inv.Z_position.y - else: - pin_y = (row+1)*self.inv.height - self.inv.Z_position.y - y_off = self.predecoder_height + pin_y - - self.add_label(text="decode_out[{0}]".format(row), - layer="metal1", - offset=[x_off, y_off]) - self.decode_out_positions.append(vector(x_off, y_off)) def create_vertical_rail(self): - # VERTICAL METAL RAILS TO CONNECT PREDECODER TO DECODE STAGE + """ Creates vertical metal 2 rails to connect predecoder and decoder stages.""" + + # This is not needed for inputs <4 since they have no pre/decode stages. if (self.num_inputs >= 4): # Array for saving the X offsets of the vertical rails. These rail # offsets are accessed with indices. - vertical_rail_x_offsets = [] + self.rail_x_offsets = [] for i in range(self.total_number_of_predecoder_outputs): - vertical_rail_x_offsets.append(-self.gap_between_rail_offset \ - * (self.total_number_of_predecoder_outputs - i)) + # The offsets go into the negative x direction + # assuming the predecodes are placed at (self.routing_width,0) + x_offset = self.metal2_pitch * i + self.rail_x_offsets.append(x_offset) self.add_rect(layer="metal2", - offset=[-self.gap_between_rail_offset * (i + 1), - 0], + offset=vector(x_offset,0), width=drc["minwidth_metal2"], height=self.height) - # Horizontal metal extensions from pre-decoder 2x4ouput. - for i in range(self.no_of_pre2x4): - self.extend_horizontal_to_pre2x4(i,vertical_rail_x_offsets) + self.connect_rails_to_predecodes() + self.connect_rails_to_decoder() - # Horizontal metal extensions from pre-decoder 3x8 ouput. - for i in range(self.no_of_pre3x8): - self.extend_horizontal_to_pre3x8(i,vertical_rail_x_offsets) + def connect_rails_to_predecodes(self): + """ Iterates through all of the predecodes and connects to the rails including the offsets """ - self.connect_vertial_rails_to_decoder(vertical_rail_x_offsets) + for i in range(self.no_of_pre2x4): + self.connect_rails_to_pre2x4(i) + + for i in range(self.no_of_pre3x8): + self.connect_rails_to_pre3x8(i) - def extend_horizontal_to_pre2x4(self, output_index, vertical_rail_x_offsets): - for inv_2x4 in range(4): - line_index = output_index * 4 + inv_2x4 - current_inv_height = (output_index * self.pre2_4.height - + inv_2x4 * (self.inv.height)) - if (inv_2x4 % 2 == 0): - pin_y = self.inv.Z_position.y + def connect_rails_to_pre2x4(self, predecode_num): + """ Connects the 2x4 predecoder outputs to the vertical rails """ + + z_pin = self.inv.get_pin("Z") + pin = z_pin.ll() + for i in range(4): + index = predecode_num * 4 + i + current_inv_height = predecode_num*self.pre2_4.height + i*self.inv.height + + if (i % 2 == 0): + pin_y = pin.y else: - pin_y = (self.inv.height - drc["minwidth_metal1"] - - self.inv.Z_position.y) - yoffset = current_inv_height + pin_y + pin_y = self.inv.height - drc["minwidth_metal1"] - pin.y - self.add_extend_rails(yoffset = yoffset, - xoffset = vertical_rail_x_offsets[line_index]) + self.connect_rail(vector(self.rail_x_offsets[index], current_inv_height + pin_y)) + + def connect_rails_to_pre3x8(self, predecode_num): + """ Connects the 3x8 predecoder outputs to the vertical rails """ - def extend_horizontal_to_pre3x8(self, output_index, vertical_rail_x_offsets): - for inv_3x8 in range(8): - line_index = output_index * 8 + inv_3x8 + self.no_of_pre2x4 * 4 - current_inv_height = output_index * (self.pre3_8.height) \ - + inv_3x8 * (self.inv.height) \ - + self.no_of_pre2x4 * self.pre2_4.height + z_pin = self.inv.get_pin("Z") + pin = z_pin.ll() + for i in range(8): + index = predecode_num * 8 + i + self.no_of_pre2x4 * 4 + current_inv_height = predecode_num*self.pre3_8.height \ + + i*self.inv.height \ + + self.no_of_pre2x4*self.pre2_4.height - if (inv_3x8 % 2 == 0): - pin_y = self.inv.Z_position.y + if (i % 2 == 0): + pin_y = pin.y else: - pin_y = (self.inv.height - drc["minwidth_metal1"] - - self.inv.Z_position.y) - yoffset = current_inv_height + pin_y + pin_y = self.inv.height - drc["minwidth_metal1"] - pin.y + + self.connect_rail(vector(self.rail_x_offsets[index], current_inv_height + pin_y)) - self.add_extend_rails(yoffset = yoffset, - xoffset = vertical_rail_x_offsets[line_index]) - - def connect_vertial_rails_to_decoder(self, vertical_rail_x_offsets): - # METAL CONNECTION FROM THE VERTICAL RAIL TOWARDS THE DECODER. - # PRE-DECODER OUTPUT ARE CONNECTED TO THIS SAME RAIL ALSO - # To makes these connections groups of line index that was stored in - # self.predecoder_output_groups are used - # Inputs of NAND2/NAND3 gates come from diffrent groups. - # For example for these groups [ [0,1,2,3] ,[4,5,6,7], - # [8,9,10,11,12,13,14,15] ] the first NAND3 inputs are connected to - # [0,4,8] and second NAND3 is connected to [0,4,9] ........... and the - # 128th NAND3 is connected to [3,7,15] + def connect_rails_to_decoder(self): + """ Use the self.predec_groups to determine the connections to the decoder NAND gates. + Inputs of NAND2/NAND3 gates come from different groups. + For example for these groups [ [0,1,2,3] ,[4,5,6,7], + [8,9,10,11,12,13,14,15] ] the first NAND3 inputs are connected to + [0,4,8] and second NAND3 is connected to [0,4,9] ........... and the + 128th NAND3 is connected to [3,7,15] + """ row_index = 0 if (self.num_inputs == 4 or self.num_inputs == 5): - for line_index_A in self.predecoder_output_groups[0]: - for line_index_B in self.predecoder_output_groups[1]: + a_pin = self.nand2.get_pin("A") + b_pin = self.nand2.get_pin("B") + + for index_A in self.predec_groups[0]: + for index_B in self.predec_groups[1]: - current_inv_height = self.predecoder_height + row_index * (self.inv.height) + current_inv_height = self.predecoder_height + row_index*self.inv.height if (row_index % 2 == 0): - yoffset_A = current_inv_height + self.nand2.A_position.y - yoffset_B = current_inv_height + self.nand2.B_position.y + yoffset_A = current_inv_height + a_pin.by() + yoffset_B = current_inv_height + b_pin.by() else: base = current_inv_height + self.inv.height - drc["minwidth_metal1"] - yoffset_A = base - self.nand2.A_position.y - yoffset_B = base - self.nand2.B_position.y + yoffset_A = base - a_pin.by() + yoffset_B = base - b_pin.by() row_index = row_index + 1 - self.add_extend_rails(yoffset =yoffset_A, - xoffset =vertical_rail_x_offsets[line_index_A]) - self.add_extend_rails(yoffset =yoffset_B, - xoffset =vertical_rail_x_offsets[line_index_B]) + self.connect_rail(vector(self.rail_x_offsets[index_A], yoffset_A)) + self.connect_rail(vector(self.rail_x_offsets[index_B], yoffset_B)) elif (self.num_inputs > 5): - for line_index_A in self.predecoder_output_groups[0]: - for line_index_B in self.predecoder_output_groups[1]: - for line_index_C in self.predecoder_output_groups[2]: + a_pin = self.nand3.get_pin("A") + b_pin = self.nand3.get_pin("B") + c_pin = self.nand3.get_pin("C") + + for index_A in self.predec_groups[0]: + for index_B in self.predec_groups[1]: + for index_C in self.predec_groups[2]: - current_inv_height = self.predecoder_height + row_index * (self.inv.height) + current_inv_height = self.predecoder_height + row_index*self.inv.height if (row_index % 2 == 0): - yoffset_A = current_inv_height + self.nand3.A_position.y - yoffset_B = current_inv_height + self.nand3.B_position.y - yoffset_C = current_inv_height + self.nand3.C_position.y + yoffset_A = current_inv_height + a_pin.by() + yoffset_B = current_inv_height + b_pin.by() + yoffset_C = current_inv_height + c_pin.by() contact_C_yoffset = yoffset_C - self.contact_shift else: base = current_inv_height + self.inv.height - drc["minwidth_metal1"] - yoffset_A = base - self.nand3.A_position.y - yoffset_B = base - self.nand3.B_position.y - yoffset_C = base - self.nand3.C_position.y + yoffset_A = base - a_pin.by() + yoffset_B = base - b_pin.by() + yoffset_C = base - c_pin.by() contact_C_yoffset = yoffset_C row_index = row_index + 1 - self.add_extend_rails(yoffset =yoffset_A, - xoffset =vertical_rail_x_offsets[line_index_A]) - self.add_extend_rails(yoffset =yoffset_B, - xoffset =vertical_rail_x_offsets[line_index_B]) - self.add_extend_rails(yoffset =yoffset_C, - xoffset =vertical_rail_x_offsets[line_index_C], - contact_yoffset = contact_C_yoffset) + self.connect_rail(vector(self.rail_x_offsets[index_A], yoffset_A)) + self.connect_rail(vector(self.rail_x_offsets[index_B], yoffset_B)) + self.connect_rail(vector(self.rail_x_offsets[index_C], yoffset_C)) # contact_C_y_offset - def add_extend_rails(self, yoffset, xoffset, contact_yoffset=0): + def route_vdd_gnd(self): + """ Add a pin for each row of vdd/gnd which are must-connects next level up. """ + + for num in range(0,self.total_number_of_predecoder_outputs + self.rows): + # this will result in duplicate polygons for rails, but who cares + + # use the inverter offset even though it will be the nand's too + (gate_offset, y_dir) = self.get_gate_offset(0, self.inv.height, num) + # route vdd + vdd_offset = gate_offset + self.inv.get_pin("vdd").ll().scale(1,y_dir) + self.add_layout_pin(text="vdd", + layer="metal1", + offset=vdd_offset, + width=self.width, + height=drc["minwidth_metal1"]) + + # route gnd + gnd_offset = gate_offset+self.inv.get_pin("gnd").ll().scale(1,y_dir) + self.add_layout_pin(text="gnd", + layer="metal1", + offset=gnd_offset, + width=self.width, + height=drc["minwidth_metal1"]) + + + def connect_rail(self, offset,contact_yoffset=0): + """ Adds a via at location and extends to self.routing_width """ self.add_rect(layer="metal1", - offset=[xoffset, yoffset], - width=-xoffset, + offset=offset, + width=self.routing_width-offset.x, height=drc["minwidth_metal1"]) if contact_yoffset!=0: yoffset = contact_yoffset self.add_via(layers=("metal1", "via1", "metal2"), - offset=[xoffset + self.gap_between_rails, - yoffset - self.via_shift], + offset=offset + vector(self.metal2_spacing,-self.via_shift), rotate=90) def delay(self, slew, load = 0.0): diff --git a/compiler/hierarchical_predecode.py b/compiler/hierarchical_predecode.py index 578914f0..08cc2407 100644 --- a/compiler/hierarchical_predecode.py +++ b/compiler/hierarchical_predecode.py @@ -6,212 +6,254 @@ from contact import contact from pinv import pinv from vector import vector from globals import OPTS +from nand_2 import nand_2 +from nand_3 import nand_3 class hierarchical_predecode(design.design): """ Pre 2x4 and 3x8 decoder shared code. """ - def __init__(self, nmos_width, cellname, input_number): - design.design.__init__(self, cellname) + def __init__(self, input_number): + self.number_of_inputs = input_number + self.number_of_outputs = int(math.pow(2, self.number_of_inputs)) + design.design.__init__(self, name="pre{0}x{1}".format(self.number_of_inputs,self.number_of_outputs)) c = reload(__import__(OPTS.config.bitcell)) self.mod_bitcell = getattr(c, OPTS.config.bitcell) - self.bitcell_height = self.mod_bitcell.chars["height"] + self.bitcell_height = self.mod_bitcell.height - self.nmos_width = nmos_width - self.number_of_inputs = input_number - self.number_of_outputs = int(math.pow(2, self.number_of_inputs)) def add_pins(self): for k in range(self.number_of_inputs): - self.add_pin("A[{0}]".format(k)) + self.add_pin("in[{0}]".format(k)) for i in range(self.number_of_outputs): self.add_pin("out[{0}]".format(i)) self.add_pin("vdd") self.add_pin("gnd") def create_modules(self): - layer_stack = ("metal1", "via1", "metal2") - self.m1m2_via = contact(layer_stack=layer_stack) - self.inv = pinv(nmos_width=drc["minwidth_tx"], - beta=2, - height=self.bitcell_height) + """ Create the INV and NAND gate """ + + self.inv = pinv() self.add_mod(self.inv) - # create_nand redefine in sub class based on number of inputs - self.create_nand() + + self.create_nand(self.number_of_inputs) self.add_mod(self.nand) - def set_up_constrain(self): - self.via_shift = (self.m1m2_via.second_layer_width - self.m1m2_via.first_layer_width) / 2 - self.metal2_extend_contact = (self.m1m2_via.second_layer_height - self.m1m2_via.contact_width) / 2 - self.via_shift = (self.m1m2_via.second_layer_width - - self.m1m2_via.first_layer_width) / 2 - self.metal2_extend_contact = (self.m1m2_via.second_layer_height - - self.m1m2_via.contact_width) / 2 + def create_nand(self,inputs): + """ Create the NAND for the predecode input stage """ + if inputs==2: + self.nand = nand_2() + elif inputs==3: + self.nand = nand_3() + else: + debug.error("Invalid number of predecode inputs.",-1) + + def setup_constraints(self): + self.m1m2_via = contact(layer_stack=("metal1", "via1", "metal2")) + self.metal2_space = drc["metal2_to_metal2"] + self.metal1_space = drc["metal1_to_metal1"] + self.metal2_width = drc["minwidth_metal2"] + self.metal1_width = drc["minwidth_metal1"] + # we are going to use horizontal vias, so use the via height + # use a conservative douple spacing just to get rid of annoying via DRCs + self.metal2_pitch = self.m1m2_via.height + 2*self.metal2_space + # This is to shift the rotated vias to be on m2 pitch + self.via_x_shift = self.m1m2_via.height + self.m1m2_via.via_layer_position.scale(0,-1).y + # This is to shift the via if the metal1 and metal2 overlaps are different + self.via_y_shift = self.m1m2_via.second_layer_position.x - self.m1m2_via.first_layer_position.x + self.m1m2_via.via_layer_position.scale(-0.5,0).x + + # The rail offsets are indexed by the label + self.rails = {} - self.gap_between_rails = (self.metal2_extend_contact - + drc["metal2_to_metal2"]) - self.gap_between_rail_offset = (self.gap_between_rails - + drc["minwidth_metal2"]) - - def setup_constrains(self): - self.via_shift = (self.m1m2_via.second_layer_width - self.m1m2_via.first_layer_width) / 2 - self.metal2_extend_contact = (self.m1m2_via.second_layer_height - self.m1m2_via.contact_width) / 2 - - # Contact shift used connecting NAND3 inputs to the rail - self.contact_shift = (self.m1m2_via.first_layer_width - self.m1m2_via.contact_width) / 2 - - self.gap_between_rails = self.metal2_extend_contact + drc["metal2_to_metal2"] - self.gap_between_rail_offset = self.gap_between_rails + drc["minwidth_metal2"] - - self.rails_x_offset = [] - # set_rail_height redefine in sub class - self.set_rail_height() - # Creating the left hand side metal2 rails for input connections - for hrail_1 in range(self.number_of_inputs): - xoffset_1 = (self.metal2_extend_contact - + (hrail_1 * self.gap_between_rail_offset)) - self.rails_x_offset.append(xoffset_1) - # x offset for Xpre2x4_inv - self.x_off_inv_1 = self.rails_x_offset[-1] + self.gap_between_rail_offset + # Non inverted input rails + for rail_index in range(self.number_of_inputs): + xoffset = rail_index * self.metal2_pitch + self.rails["in[{}]".format(rail_index)]=xoffset + # x offset for input inverters + self.x_off_inv_1 = self.number_of_inputs*self.metal2_pitch + # Creating the right hand side metal2 rails for output connections - for hrail_2 in range(2 * self.number_of_inputs + 2): - xoffset_2 = self.x_off_inv_1 + self.inv.width + self.gap_between_rails + (hrail_2 * self.gap_between_rail_offset) - self.rails_x_offset.append(xoffset_2) - self.xoffset_2=self.rails_x_offset[-1] + for rail_index in range(2 * self.number_of_inputs): + xoffset = self.x_off_inv_1 + self.inv.width + ((rail_index+1) * self.metal2_pitch) + if rail_index < self.number_of_inputs: + self.rails["Abar[{}]".format(rail_index)]=xoffset + else: + self.rails["A[{}]".format(rail_index-self.number_of_inputs)]=xoffset - self.x_off_nand = self.xoffset_2 + self.gap_between_rail_offset + # x offset to NAND decoder includes the left rails, mid rails and inverters, plus an extra m2 pitch + self.x_off_nand = self.x_off_inv_1 + self.inv.width + (1 + 2*self.number_of_inputs) * self.metal2_pitch + + + # x offset to output inverters self.x_off_inv_2 = self.x_off_nand + self.nand.width - self.update_size() - def update_size(self): + # Height width are computed self.width = self.x_off_inv_2 + self.inv.width - self.set_height() - self.size = vector(self.width, self.height) - correct =vector(0, 0.5 * drc["minwidth_metal1"]) - self.vdd_position = self.size - correct - vector(0, self.inv.height) - self.gnd_position = self.size - correct + self.height = self.number_of_outputs * self.nand.height def create_rails(self): - for x_off in self.rails_x_offset: - self.add_rect(layer="metal2", - offset=[x_off, 0], - width=drc["minwidth_metal2"], - height=self.rail_height) - - def add_output_inverters(self): - self.decode_out_positions = [] - for inv_2x4 in range(self.number_of_outputs): - name = "Xpre2x4_nand_inv[{0}]".format(inv_2x4) - if (inv_2x4 % 2 == 0): - y_factor = inv_2x4 - mirror = "R0" - correct = self.inv.Z_position + """ Create all of the rails for the inputs and vdd/gnd/inputs_bar/inputs """ + for label in self.rails.keys(): + # these are not primary inputs, so they shouldn't have a + # label or LVS complains about different names on one net + if label.startswith("in"): + self.add_layout_pin(text=label, + layer="metal2", + offset=[self.rails[label], 0], + width=self.metal2_width, + height=self.height - drc["metal2_to_metal2"]) else: - y_factor =inv_2x4 + 1 - mirror = "MX" - correct = self.inv.Z_position.scale(1,-1) - vector(0, - drc["minwidth_metal1"]) + self.add_rect(layer="metal2", + offset=[self.rails[label], 0], + width=self.metal2_width, + height=self.height - drc["metal2_to_metal2"]) + + def add_input_inverters(self): + """ Create the input inverters to invert input signals for the decode stage. """ + for inv_num in range(self.number_of_inputs): + name = "Xpre_inv[{0}]".format(inv_num) + if (inv_num % 2 == 0): + y_off = inv_num * (self.inv.height) + offset = vector(self.x_off_inv_1, y_off) + mirror = "R0" + else: + y_off = (inv_num + 1) * (self.inv.height) + offset = vector(self.x_off_inv_1, y_off) + mirror="MX" + self.add_inst(name=name, + mod=self.inv, + offset=offset, + mirror=mirror) + self.connect_inst(["in[{0}]".format(inv_num), + "inbar[{0}]".format(inv_num), + "vdd", "gnd"]) + + def add_output_inverters(self): + """ Create inverters for the inverted output decode signals. """ + + self.decode_out_positions = [] + z_pin = self.inv.get_pin("Z") + for inv_num in range(self.number_of_outputs): + name = "Xpre2x4_nand_inv[{}]".format(inv_num) + if (inv_num % 2 == 0): + y_factor = inv_num + mirror = "R0" + y_dir = 1 + else: + y_factor =inv_num + 1 + mirror = "MX" + y_dir = -1 base = vector(self.x_off_inv_2, self.inv.height * y_factor) self.add_inst(name=name, mod=self.inv, offset=base, mirror=mirror) - self.connect_inst(["Z[{0}]".format(inv_2x4), - "out[{0}]".format(inv_2x4), + self.connect_inst(["Z[{}]".format(inv_num), + "out[{}]".format(inv_num), "vdd", "gnd"]) - output_inv_out_offset = base + correct - self.decode_out_positions.append(output_inv_out_offset) + + z_pin = self.inv.get_pin("Z") + self.add_layout_pin(text="out[{}]".format(inv_num), + layer="metal1", + offset=base+z_pin.ll().scale(1,y_dir), + width=z_pin.width(), + height=z_pin.height()*y_dir) + def add_nand(self,connections): + """ Create the NAND stage for the decodes """ + z_pin = self.nand.get_pin("Z") + a_pin = self.inv.get_pin("A") for nand_input in range(self.number_of_outputs): inout = str(self.number_of_inputs)+"x"+str(self.number_of_outputs) - name = "Xpre"+inout+"_nand[{0}]".format(nand_input) + name = "Xpre{0}_nand[{1}]".format(inout,nand_input) + rect_height = z_pin.uy()-a_pin.by() if (nand_input % 2 == 0): y_off = nand_input * (self.nand.height) mirror = "R0" - offset = [self.x_off_nand + self.nand.width, - y_off + self.nand.Z_position.y] + rect_offset = vector(self.x_off_nand + self.nand.width, + y_off + z_pin.uy() - rect_height) else: y_off = (nand_input + 1) * (self.nand.height) mirror = "MX" - offset =[self.x_off_nand + self.nand.width, - y_off - self.nand.Z_position.y - drc["minwidth_metal1"]] + rect_offset =vector(self.x_off_nand + self.nand.width, + y_off - z_pin.uy()) self.add_inst(name=name, mod=self.nand, offset=[self.x_off_nand, y_off], mirror=mirror) self.add_rect(layer="metal1", - offset=offset, - width=drc["minwidth_metal1"], - height=drc["minwidth_metal1"]) + offset=rect_offset, + width=self.metal1_width, + height=rect_height) self.connect_inst(connections[nand_input]) def route(self): self.route_input_inverters() + self.route_inputs_to_rails() self.route_nand_to_rails() - self.route_vdd_gnd_from_rails_to_gates() + self.route_vdd_gnd() - def route_input_inverters(self): - # All conections of the inputs inverters [Inputs, outputs, vdd, gnd] - output_shift = self.set_output_shift() - for inv_rout in range(self.number_of_inputs): - setup = self.setup_route_input_inverter(inv_rout,output_shift) - y_dir,inv_in_offset,inv_out_offset,inv_vdd_offset,inv_gnd_offset = setup - #add output - correct = y_dir * (output_shift + drc["minwidth_metal1"]) - output_metal = self.cal_input_inverters_output(setup,output_shift,inv_rout) - offset1,offset2=output_metal[0] - offset3,offset4=output_metal[1] + def route_inputs_to_rails(self): + """ Route the uninverted inputs to the second set of rails """ + for num in range(self.number_of_inputs): + # route one signal next to each vdd/gnd rail since this is + # typically where the p/n devices are and there are no + # pins in the nand gates. + y_offset = (num+self.number_of_inputs) * self.inv.height + 2*self.metal1_space + in_pin = "in[{}]".format(num) + a_pin = "A[{}]".format(num) self.add_rect(layer="metal1", - offset=offset1, - width=drc["minwidth_metal1"], - height=offset2.y - offset1.y) - self.add_rect(layer="metal1", - offset=offset3, - width=offset4.x - offset3.x, - height=drc["minwidth_metal1"]) - off_via = [self.rails_x_offset[inv_rout + self.number_of_inputs+2] + self.gap_between_rails, - inv_vdd_offset.y- self.via_shift - correct] + offset=[self.rails[in_pin],y_offset], + width=self.rails[a_pin] + self.metal2_width - self.rails[in_pin], + height=self.metal1_width) self.add_via(layers = ("metal1", "via1", "metal2"), - offset=off_via, + offset=[self.rails[in_pin] + self.via_x_shift, y_offset + self.via_y_shift], rotate=90) - #route input - self.add_rect(layer="metal1", - offset=[self.rails_x_offset[inv_rout], - inv_in_offset.y], - width=inv_in_offset.x - self.rails_x_offset[inv_rout] + drc["minwidth_metal2"], - height=drc["minwidth_metal1"]) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=[self.rails_x_offset[inv_rout] + self.gap_between_rails, - inv_in_offset.y - self.via_shift], + self.add_via(layers = ("metal1", "via1", "metal2"), + offset=[self.rails[a_pin] + self.via_x_shift, y_offset + self.via_y_shift], rotate=90) - # route vdd + + def route_input_inverters(self): + """ + Route all conections of the inputs inverters [Inputs, outputs, vdd, gnd] + """ + for inv_num in range(self.number_of_inputs): + (inv_offset, y_dir) = self.get_gate_offset(self.x_off_inv_1, self.inv.height, inv_num) + + out_pin = "Abar[{}]".format(inv_num) + in_pin = "in[{}]".format(inv_num) + + #add output so that it is just below the vdd or gnd rail + # since this is where the p/n devices are and there are no + # pins in the nand gates. + y_offset = (inv_num+1) * self.inv.height - 3*self.metal1_space + inv_out_offset = inv_offset+self.inv.get_pin("Z").ur().scale(1,y_dir)-vector(0,self.metal1_width).scale(1,y_dir) self.add_rect(layer="metal1", - offset=inv_vdd_offset, - width=self.rails_x_offset[self.number_of_inputs] - inv_vdd_offset.x + drc["minwidth_metal2"], - height=drc["minwidth_metal1"]) - # route gnd + offset=[inv_out_offset.x,y_offset], + width=self.rails[out_pin]-inv_out_offset.x + self.metal2_width, + height=self.metal1_width) self.add_rect(layer="metal1", - offset=inv_gnd_offset, - width=self.rails_x_offset[self.number_of_inputs+1] - inv_gnd_offset.x + drc["minwidth_metal2"], - height=drc["minwidth_metal1"]) + offset=inv_out_offset, + width=self.metal1_width, + height=y_offset-inv_out_offset.y) + self.add_via(layers = ("metal1", "via1", "metal2"), + offset=[self.rails[out_pin] + self.via_x_shift, y_offset + self.via_y_shift], + rotate=90) - def setup_route_input_inverter(self, inv_rout, output_shift): - # add Inputs, vdd, gnd of the inputs inverters - if (inv_rout % 2 == 0): - base_offset=[self.x_off_inv_1, inv_rout * self.inv.height ] - y_dir = 1 - else: - base_offset=[self.x_off_inv_1, 2 * self.inv.height - drc["minwidth_metal1"]] - y_dir = -1 - inv_out_offset = base_offset+self.inv.Z_position.scale(1,y_dir) - inv_in_offset = base_offset+self.inv.A_position.scale(1,y_dir) - inv_vdd_offset = base_offset+self.inv.vdd_position.scale(1,y_dir) - inv_gnd_offset = base_offset+self.inv.gnd_position.scale(1,y_dir) - #return info to create output of the input inverter - return [y_dir,inv_in_offset,inv_out_offset,inv_vdd_offset,inv_gnd_offset] + + #route input + inv_in_offset = inv_offset+self.inv.get_pin("A").ll().scale(1,y_dir) + self.add_rect(layer="metal1", + offset=[self.rails[in_pin], inv_in_offset.y], + width=inv_in_offset.x - self.rails[in_pin], + height=self.metal1_width) + self.add_via(layers=("metal1", "via1", "metal2"), + offset=[self.rails[in_pin] + self.via_x_shift, inv_in_offset.y + self.via_y_shift], + rotate=90) + def route_nand_to_rails(self): # This 2D array defines the connection mapping @@ -219,65 +261,50 @@ class hierarchical_predecode(design.design): for k in range(self.number_of_outputs): # create x offset list index_lst= nand_input_line_combination[k] - line_x_offset = [] - for index in index_lst: - line_x_offset.append(self.rails_x_offset[index]) - # create y offset list - yoffset_nand_in, correct= self.create_y_offsets(k) - # connect based on the two list - for i in range(self.number_of_inputs): - x_offset = line_x_offset[i] - y_offset = yoffset_nand_in[i] - # Connecting the i-th input of Nand3 gate + (nand_offset,y_dir) = self.get_gate_offset(self.x_off_nand,self.nand.height,k) + + if self.number_of_inputs == 2: + gate_lst = ["A","B"] + else: + gate_lst = ["A","B","C"] + + # this will connect pins A,B or A,B,C + for rail_pin,gate_pin in zip(index_lst,gate_lst): + pin_offset = nand_offset+self.nand.get_pin(gate_pin).ll().scale(1,y_dir) self.add_rect(layer="metal1", - offset=[x_offset, y_offset], - width=self.x_off_nand - x_offset, - height=drc["minwidth_metal1"]) + offset=[self.rails[rail_pin], pin_offset.y], + width=pin_offset.x - self.rails[rail_pin], + height=self.metal1_width) self.add_via(layers=("metal1", "via1", "metal2"), - offset=[x_offset+ self.gap_between_rails, - y_offset - self.via_shift - correct[i]], - rotate=90) - # Extended of the top NAND2 to the left hand side input rails - if(k == self.number_of_outputs - 1): - x_offset = self.rails_x_offset[i] - self.add_rect(layer="metal1", - offset=[x_offset, y_offset], - width=self.x_off_nand - x_offset, - height=drc["minwidth_metal1"]) - self.add_via(layers = ("metal1", "via1", "metal2"), - offset=[x_offset + self.gap_between_rails, - y_offset - self.via_shift], - rotate=90) + offset=[self.rails[rail_pin] + self.via_x_shift, pin_offset.y + self.via_y_shift], + rotate=90) + + + + def route_vdd_gnd(self): + """ Add a pin for each row of vdd/gnd which are must-connects next level up. """ + + for num in range(0,self.number_of_outputs): + # this will result in duplicate polygons for rails, but who cares + + # use the inverter offset even though it will be the nand's too + (gate_offset, y_dir) = self.get_gate_offset(0, self.inv.height, num) + + # route vdd + vdd_offset = gate_offset + self.inv.get_pin("vdd").ll().scale(1,y_dir) + self.add_layout_pin(text="vdd", + layer="metal1", + offset=vdd_offset, + width=self.x_off_inv_2 + self.inv.width + self.metal2_width, + height=self.metal1_width) + + # route gnd + gnd_offset = gate_offset+self.inv.get_pin("gnd").ll().scale(1,y_dir) + self.add_layout_pin(text="gnd", + layer="metal1", + offset=gnd_offset, + width=self.x_off_inv_2 + self.inv.width + self.metal2_width, + height=self.metal1_width) + - def route_vdd_gnd_from_rails_to_gates(self): - via_correct = self.get_via_correct() - for k in range(self.number_of_outputs): - power_line_index = self.number_of_inputs + 1 - (k%2) - yoffset = k * self.inv.height - 0.5 * drc["minwidth_metal1"] - self.add_rect(layer="metal1", - offset=[self.rails_x_offset[power_line_index], - yoffset], - width=self.x_off_nand - self.rails_x_offset[power_line_index], - height=drc["minwidth_metal1"]) - self.add_via(layers = ("metal1", "via1", "metal2"), - offset=[self.rails_x_offset[power_line_index] + self.gap_between_rails, - yoffset - via_correct.y], - rotate=90) - yoffset = (self.number_of_outputs * self.inv.height - - 0.5 * drc["minwidth_metal1"]) - v_metal = self.get_vertical_metal() - via_y = self.get_via_y() - index = self.number_of_inputs + 1 - self.add_rect(layer="metal1", - offset=[self.rails_x_offset[index], yoffset], - width=self.x_off_nand - self.rails_x_offset[index], - height=drc["minwidth_metal1"]) - self.add_rect(layer=v_metal, - offset=[self.rails_x_offset[index], self.rail_height], - width=drc["minwidth_"+v_metal], - height=yoffset - self.rail_height) - self.add_via(layers = ("metal1", "via1", "metal2"), - offset=[self.rails_x_offset[index] + self.gap_between_rails, - via_y] - via_correct, - rotate=90) diff --git a/compiler/hierarchical_predecode2x4.py b/compiler/hierarchical_predecode2x4.py index c8b871c3..f6fafedb 100644 --- a/compiler/hierarchical_predecode2x4.py +++ b/compiler/hierarchical_predecode2x4.py @@ -1,7 +1,6 @@ from tech import drc import debug import design -from nand_2 import nand_2 from vector import vector from hierarchical_predecode import hierarchical_predecode @@ -9,110 +8,46 @@ class hierarchical_predecode2x4(hierarchical_predecode): """ Pre 2x4 decoder used in hierarchical_decoder. """ - def __init__(self, nmos_width, cellname): - hierarchical_predecode.__init__(self, nmos_width, cellname, 2) + def __init__(self): + hierarchical_predecode.__init__(self, 2) self.add_pins() self.create_modules() - self.setup_constrains() + self.setup_constraints() self.create_layout() - self.route() - - def create_nand(self): - self.nand = nand_2(nmos_width=self.nmos_width, - height=self.bitcell_height) - - def set_rail_height(self): - self.rail_height = (self.number_of_outputs * self.nand.height - - (self.number_of_outputs - 1) * drc["minwidth_metal2"]) + self.DRC_LVS() def create_layout(self): + """ The general organization is from left to right: + 1) a set of M2 rails for input signals + 2) a set of inverters to invert input signals + 3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs + 4) a set of NAND gates for inversion + """ self.create_rails() - self.add_inv2x4() + self.add_input_inverters() self.add_output_inverters() - connections =[["A[0]", "A[1]", "Z[3]", "vdd", "gnd"], - ["B[0]", "A[1]", "Z[2]", "vdd", "gnd"], - ["A[0]", "B[1]", "Z[1]", "vdd", "gnd"], - ["B[0]", "B[1]", "Z[0]", "vdd", "gnd"]] + connections =[["in[0]", "in[1]", "Z[3]", "vdd", "gnd"], + ["inbar[0]", "in[1]", "Z[2]", "vdd", "gnd"], + ["in[0]", "inbar[1]", "Z[1]", "vdd", "gnd"], + ["inbar[0]", "inbar[1]", "Z[0]", "vdd", "gnd"]] self.add_nand(connections) - - def set_height(self): - self.height = 4 * self.nand.height - - def add_inv2x4(self): - self.A_positions = [] - for inv_2x4 in range(self.number_of_inputs): - name = "Xpre2x4_inv[{0}]".format(inv_2x4) - if (inv_2x4 % 2 == 0): - y_off = inv_2x4 * (self.inv.height) - offset = vector(self.x_off_inv_1, y_off) - mirror = "R0" - A_off = self.inv.A_position.scale(0, 1) - else: - y_off = (inv_2x4 + 1) * (self.inv.height) - offset = vector(self.x_off_inv_1, y_off) - mirror="MX" - A_off = vector(0, - self.inv.A_position.y - drc["minwidth_metal1"]) - self.A_positions.append(offset + A_off) - self.add_inst(name=name, - mod=self.inv, - offset=offset, - mirror=mirror) - self.connect_inst(["A[{0}]".format(inv_2x4), - "B[{0}]".format(inv_2x4), - "vdd", "gnd"]) - - def cal_input_inverters_output(self,setup,output_shift,inv_rout): - y_dir,inv_in_offset,inv_out_offset,inv_vdd_offset,inv_gnd_offset = setup - correct = y_dir * (output_shift + drc["minwidth_metal1"]) - out_offset = vector(inv_out_offset) - if y_dir == -1: - out_offset.y = inv_vdd_offset.y + output_shift + drc["minwidth_metal1"] - - vertical1 = out_offset - vertical2 = vertical1 + vector(0, - (inv_vdd_offset.y - inv_out_offset.y) * y_dir - - output_shift) - horizontal1 = vector(inv_out_offset.x, - inv_vdd_offset.y - correct) - horizontal2 = horizontal1 + vector(self.rails_x_offset[inv_rout + 4] - inv_out_offset.x+ drc["minwidth_metal2"], - 0) - return [[vertical1,vertical2],[horizontal1,horizontal2]] - - def set_output_shift(self): - return 2 * drc["minwidth_metal1"] + self.route() def get_nand_input_line_combination(self): - combination = [[4, 5], [6, 5], [4, 7], [6, 7]] + """ These are the decoder connections of the NAND gates to the A,B pins """ + combination = [["Abar[0]", "Abar[1]"], + ["A[0]", "Abar[1]"], + ["Abar[0]", "A[1]"], + ["A[0]", "A[1]"]] return combination - def create_y_offsets(self,k): - # create y offset list - if (k % 2 == 0): - y_off = k * (self.nand.height) - direct = 1 - else: - y_off = (k + 1) * (self.nand.height) - drc["minwidth_metal1"] - direct = - 1 - correct =[0,0] - yoffset_nand_in = [y_off + direct * self.nand.A_position.y, - y_off + direct * self.nand.B_position.y] - return yoffset_nand_in, correct - - def get_via_correct(self): - return vector(0, self.via_shift) - - def get_vertical_metal(self): - return "metal1" - - def get_via_y(self): - return self.rail_height def delay(self, slew, load = 0.0 ): - # A -> B + # in -> inbar a_t_b_delay = self.inv.delay(slew=slew,load = self.nand.input_load()) - # out -> z + # inbar -> z b_t_z_delay = self.nand.delay(slew=a_t_b_delay.slew,load = self.inv.input_load()) result = a_t_b_delay + b_t_z_delay @@ -122,4 +57,4 @@ class hierarchical_predecode2x4(hierarchical_predecode): return result def input_load(self): - return self.inv.input_load() + return self.nand.input_load() diff --git a/compiler/hierarchical_predecode3x8.py b/compiler/hierarchical_predecode3x8.py index 681ea602..f5e2cc4f 100644 --- a/compiler/hierarchical_predecode3x8.py +++ b/compiler/hierarchical_predecode3x8.py @@ -1,105 +1,69 @@ from tech import drc import debug import design -from nand_3 import nand_3 from vector import vector from hierarchical_predecode import hierarchical_predecode - class hierarchical_predecode3x8(hierarchical_predecode): """ Pre 3x8 decoder used in hierarchical_decoder. """ - def __init__(self, nmos_width, cellname): - hierarchical_predecode.__init__(self, nmos_width, cellname, 3) + def __init__(self): + hierarchical_predecode.__init__(self, 3) self.add_pins() self.create_modules() - self.setup_constrains() + self.setup_constraints() self.create_layout() + self.DRC_LVS() + + def create_layout(self): + """ The general organization is from left to right: + 1) a set of M2 rails for input signals + 2) a set of inverters to invert input signals + 3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs + 4) a set of NAND gates for inversion + """ + self.create_rails() + self.add_input_inverters() + self.add_output_inverters() + connections=[["in[0]", "in[1]", "in[2]", "Z[7]", "vdd", "gnd"], + ["in[0]", "in[1]", "inbar[2]", "Z[6]", "vdd", "gnd"], + ["in[0]", "inbar[1]", "in[2]", "Z[5]", "vdd", "gnd"], + ["in[0]", "inbar[1]", "inbar[2]", "Z[4]", "vdd", "gnd"], + ["inbar[0]", "in[1]", "in[2]", "Z[3]", "vdd", "gnd"], + ["inbar[0]", "in[1]", "inbar[2]", "Z[2]", "vdd", "gnd"], + ["inbar[0]", "inbar[1]", "in[2]", "Z[1]", "vdd", "gnd"], + ["inbar[0]", "inbar[1]", "inbar[2]", "Z[0]", "vdd", "gnd"]] + self.add_nand(connections) self.route() - def create_nand(self): - self.nand = nand_3(nmos_width=self.nmos_width, - height=self.bitcell_height) - - def set_rail_height(self): - self.rail_height = (self.number_of_outputs * self.nand.height - - 1.5 * drc["minwidth_metal2"]) - def create_layout(self): - self.create_rails() - self.add_output_inverters() - connections=[["A[0]", "A[1]", "A[2]", "Z[7]", "vdd", "gnd"], - ["A[0]", "A[1]", "B[2]", "Z[6]", "vdd", "gnd"], - ["A[0]", "B[1]", "A[2]", "Z[5]", "vdd", "gnd"], - ["A[0]", "B[1]", "B[2]", "Z[4]", "vdd", "gnd"], - ["B[0]", "A[1]", "A[2]", "Z[3]", "vdd", "gnd"], - ["B[0]", "A[1]", "B[2]", "Z[2]", "vdd", "gnd"], - ["B[0]", "B[1]", "A[2]", "Z[1]", "vdd", "gnd"], - ["B[0]", "B[1]", "B[2]", "Z[0]", "vdd", "gnd"]] - self.add_nand(connections) - - def set_height(self): - self.height = 8 * self.nand.height - - def cal_input_inverters_output(self,setup,output_shift,inv_rout): - y_dir,inv_in_offset,inv_out_offset,inv_vdd_offset,inv_gnd_offset = setup - correct = y_dir * (output_shift + drc["minwidth_metal1"]) - - out_offset = inv_out_offset + vector(0, output_shift + correct) - vertical1 = out_offset - vertical2 = (vertical1.scale(1, 0) + inv_vdd_offset.scale(0, 1) - + vector(0, - correct)) - horizontal1 = vertical1 - horizontal2 = vector(self.rails_x_offset[inv_rout + 5] + drc["minwidth_metal2"], - vertical2.y) - return [[vertical1,vertical2],[horizontal1,horizontal2]] - - def set_output_shift(self): - return 1.5 * drc["minwidth_metal1"] - def get_nand_input_line_combination(self): - combination = [[5, 6, 7], [5, 6, 10], - [5, 9, 7], [5, 9, 10], - [8, 6, 7], [8, 6, 10], - [8, 9, 7], [8, 9, 10]] + """ These are the decoder connections of the NAND gates to the A,B,C pins """ + combination = [["Abar[0]", "Abar[1]", "Abar[2]"], + ["Abar[0]", "Abar[1]", "A[2]"], + ["Abar[0]", "A[1]", "Abar[2]"], + ["Abar[0]", "A[1]", "A[2]"], + ["A[0]", "Abar[1]", "Abar[2]"], + ["A[0]", "Abar[1]", "A[2]"], + ["A[0]", "A[1]", "Abar[2]"], + ["A[0]", "A[1]", "A[2]"]] return combination - def create_y_offsets(self,k): - if (k % 2 == 0): - y_off = k * (self.nand.height) - y_dir =1 - correct = [0,0,self.contact_shift] - else: - y_off = 2 * self.inv.height - drc["minwidth_metal1"] + (k - 1) * (self.nand.height) - y_dir = -1 - correct = [0,self.contact_shift,0] - yoffset_nand_in = [y_off + y_dir*self.nand.A_position[1], - y_off + y_dir*self.nand.B_position[1], - y_off + y_dir*self.nand.C_position[1]] - return yoffset_nand_in, correct - - def get_via_correct(self): - return vector(0, self.via_shift+self.contact_shift) - - def get_vertical_metal(self): - return "metal2" - - def get_via_y(self): - yoffset = (self.number_of_outputs * self.inv.height - - 0.5 * drc["minwidth_metal1"]) - return yoffset def delay(self, slew, load = 0.0 ): - # A -> z - b_t_z_delay = self.nand.delay(slew=slew, - load = self.input_load()) + # A -> Abar + a_t_b_delay = self.inv.delay(slew=slew,load = self.nand.input_load()) + + # Abar -> z + b_t_z_delay = self.nand.delay(slew=a_t_b_delay.slew,load = self.inv.input_load()) + result = a_t_b_delay + b_t_z_delay # Z -> out - a_t_out_delay = self.inv.delay(slew=b_t_z_delay.slew, - load = load) - result = b_t_z_delay + a_t_out_delay + a_t_out_delay = self.inv.delay(slew=b_t_z_delay.slew,load = load) + result = result + a_t_out_delay return result + def input_load(self): return self.nand.input_load() diff --git a/compiler/hierarchy_layout.py b/compiler/hierarchy_layout.py index c03cab90..0d36e60f 100644 --- a/compiler/hierarchy_layout.py +++ b/compiler/hierarchy_layout.py @@ -6,6 +6,7 @@ from tech import drc, GDS from tech import layer as techlayer import os from vector import vector +from pin_layout import pin_layout class layout: """ @@ -21,9 +22,9 @@ class layout: self.name = name self.width = None self.height = None - self.insts = [] # Holds module/cell layout instances - self.objs = [] # Holds all other objects (labels, geometries, etc) - + self.insts = [] # Holds module/cell layout instances + self.objs = [] # Holds all other objects (labels, geometries, etc) + self.pin_map = {} # Holds name->(vector,layer) map for all pins self.visited = False # Flag for traversing the hierarchy self.gds_read() @@ -38,6 +39,21 @@ class layout: self.offset_attributes(coordinate) self.translate(coordinate) + def get_gate_offset(self, x_offset, height, inv_num): + """Gets the base offset and y orientation of stacked rows of gates + assuming a minwidth metal1 vdd/gnd rail. Input is which gate + in the stack from 0..n + """ + + if (inv_num % 2 == 0): + base_offset=vector(x_offset, inv_num * height) + y_dir = 1 + else: + # we lose a rail after every 2 gates + base_offset=vector(x_offset, (inv_num+1) * height - (inv_num%2)*drc["minwidth_metal1"]) + y_dir = -1 + + return (base_offset,y_dir) def find_lowest_coords(self): @@ -99,24 +115,46 @@ class layout: for inst in self.insts: inst.offset = vector(inst.offset - coordinate) - # FIXME: Make name optional and pick a random one if not specified def add_inst(self, name, mod, offset=[0,0], mirror="R0",rotate=0): """Adds an instance of a mod to this module""" self.insts.append(geometry.instance(name, mod, offset, mirror, rotate)) - message = [] - for x in self.insts: - message.append(x.name) - debug.info(4, "adding instance" + ",".join(message)) + debug.info(4, "adding instance" + ",".join(x.name for x in self.insts)) + return self.insts[-1] + def get_inst(self, name): + """Retrieve an instance by name""" + for inst in self.insts: + if inst.name == name: + return inst + return None + def add_rect(self, layer, offset, width, height): """Adds a rectangle on a given layer,offset with width and height""" # negative layers indicate "unused" layers in a given technology layerNumber = techlayer[layer] if layerNumber >= 0: self.objs.append(geometry.rectangle(layerNumber, offset, width, height)) + return self.objs[-1] + return None + + + def get_pin(self, text): + """ Return the pin or list of pins """ + debug.check(len(self.pin_map[text])==1,"Should use a pin iterator since more than one pin.") + # If we have one pin, return it and not the list. + # Otherwise, should use get_pins() + return self.pin_map[text][0] - def add_layout_pin(self, text, layer, offset, width, height): + def get_pins(self, text): + """ Return a pin list (instead of a single pin) """ + return self.pin_map[text] + + def add_layout_pin(self, text, layer, offset, width=None, height=None): """Create a labeled pin""" + if width==None: + width=drc["minwidth_{0}".format(layer)] + if height==None: + height=drc["minwidth_{0}".format(layer)] self.add_rect(layer=layer, offset=offset, width=width, @@ -124,7 +162,12 @@ class layout: self.add_label(text=text, layer=layer, offset=offset) - + + try: + self.pin_map[text].append(pin_layout(text,vector(offset,offset+vector(width,height)),layer)) + except KeyError: + self.pin_map[text] = [pin_layout(text,vector(offset,offset+vector(width,height)),layer)] + def add_label(self, text, layer, offset=[0,0],zoom=-1): """Adds a text label on the given layer,offset, and zoom level""" @@ -132,6 +175,8 @@ class layout: layerNumber = techlayer[layer] if layerNumber >= 0: self.objs.append(geometry.label(text, layerNumber, offset, zoom)) + return self.objs[-1] + return None def add_path(self, layer, coordinates, width=None): diff --git a/compiler/hierarchy_spice.py b/compiler/hierarchy_spice.py index bf807bba..0d90940f 100644 --- a/compiler/hierarchy_spice.py +++ b/compiler/hierarchy_spice.py @@ -45,8 +45,8 @@ class spice: def connect_inst(self, args, check=True): """Connects the pins of the last instance added - It is preferred to use the other function with the check to find if - there is a problem. The check otion can be set to false + It is preferred to use the function with the check to find if + there is a problem. The check option can be set to false where we dynamically generate groups of connections after a group of modules are generated.""" diff --git a/compiler/logic_effort_dc.py b/compiler/logic_effort_dc.py deleted file mode 100644 index b3283fc1..00000000 --- a/compiler/logic_effort_dc.py +++ /dev/null @@ -1,188 +0,0 @@ -import debug -import design -from tech import drc -from pinv import pinv -from contact import contact -from vector import vector -from globals import OPTS - -class logic_effort_dc(design.design): - """ - Generate a logic effort based delay chain. - Input is a list contains the electrical effort of each stage. - """ - - def __init__(self, name, stage_list): - """init function""" - design.design.__init__(self, "delay_chain") - #fix me: input should be logic effort value - # and there should be functions to get - # area effecient inverter stage list - - # chain_length is number of inverters in the load - # plus 1 for the input - chain_length = 1 + sum(stage_list) - # half chain length is the width of the layeout - # invs are stacked into 2 levels so input/output are close - half_length = round(chain_length / 2.0) - - c = reload(__import__(OPTS.config.bitcell)) - self.mod_bitcell = getattr(c, OPTS.config.bitcell) - self.bitcell_height = self.mod_bitcell.chars["height"] - - self.add_pins() - self.create_module() - self.cal_cell_size(half_length) - self.create_inv_stage_lst(stage_list) - self.add_inv_lst(chain_length) - self.route_inv(stage_list) - self.add_vddgnd_label() - self.DRC_LVS() - - def add_pins(self): - """add the pins of the delay chain""" - self.add_pin("clk_in") - self.add_pin("clk_out") - self.add_pin("vdd") - self.add_pin("gnd") - - def cal_cell_size(self, half_length): - """ calculate the width and height of the cell""" - self.width = half_length * self.inv.width - self.height = 2 * self.bitcell_height - - def create_module(self): - """add the inverters""" - self.inv = pinv(nmos_width=drc["minwidth_tx"], - route_output=False) - self.add_mod(self.inv) - - - def create_inv_stage_lst(self, stage_list): - """ Generate a list to indicate what stage each inv is in """ - self.inv_stage_lst = [[0, True]] - stage_num = 0 - for stage in stage_list: - stage_num = stage_num + 1 - repeat_times = stage - for i in range(repeat_times): - if i == repeat_times - 1: - # the first one need to connected to the next stage - self.inv_stage_lst.append([stage_num, True]) - else: - # the rest should not drive any thing - self.inv_stage_lst.append([stage_num, False]) - - def add_inv_lst(self, chain_length): - """add the inverter and connect them based on the stage list """ - half_length = round(chain_length / 2.0) - self.inv_positions = [] - for i in range(chain_length): - if i < half_length: - # add top level - inv_offset = [i * self.inv.width, - 2 * self.inv.height] - inv_mirror="MX" - self.inv_positions.append(inv_offset) - offset = inv_offset + \ - self.inv.input_position.scale(1,-1) - m1m2_via_rotate=270 - if i == 0: - self.clk_in_offset = offset - else: - # add bottom level - inv_offset = [(chain_length - i) * self.inv.width, - 0] - inv_mirror="MY" - self.inv_positions.append(inv_offset) - offset = inv_offset + \ - self.inv.input_position.scale(-1,1) - m1m2_via_rotate=90 - if i == chain_length - 1: - self.clk_out_offset = inv_offset + \ - self.inv.output_position.scale(-1,1) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=offset, - rotate=m1m2_via_rotate) - self.add_inst(name="inv_chain%d" % i, - mod=self.inv, - offset=inv_offset, - mirror=inv_mirror) - - # connecting spice - if i == 0: - self.connect_inst(args=["clk_in", "s" + str(self.inv_stage_lst[i][0] + 1), - "vdd", "gnd"], - check=False) - spare_node_counter = 1 - elif i == chain_length - 1: - self.connect_inst(args=["s" + str(self.inv_stage_lst[i][0]), "clk_out", "vdd", "gnd"], - check=False) - else: - if self.inv_stage_lst[i][1] == True: - self.connect_inst(args=["s" + str(self.inv_stage_lst[i][0]), - "s" + str(self.inv_stage_lst[i][0] + 1), "vdd", "gnd"], - check=False) - spare_node_counter = 1 - else: - self.connect_inst(args=["s" + str(self.inv_stage_lst[i][0]), "s" \ - + str(self.inv_stage_lst[i][0] + 1) + "n" \ - + str(spare_node_counter), "vdd", "gnd"], - check=False) - spare_node_counter += 1 - - def route_inv(self, stage_list): - """add metal routing based on the stage list """ - half_length = round((sum(stage_list) + 1) / 2.0) - start_inv = end_inv = 0 - for stage in stage_list: - # end inv number depends on the fan out number - end_inv = start_inv + stage - start_inv_offset = self.inv_positions[start_inv] - end_inv_offset = self.inv_positions[end_inv] - - if start_inv < half_length: - start_o_offset = start_inv_offset + \ - self.inv.output_position.scale(1,-1) - m1m2_via_rotate =270 - m1m2_via_vc = vector(1,-.5) - else: - start_o_offset = start_inv_offset + \ - self.inv.output_position.scale(-1,1) - m1m2_via_rotate =90 - m1m2_via_vc = vector(1,.5) - M2_start = start_o_offset + vector(0,drc["minwidth_metal2"]).scale(m1m2_via_vc) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=start_o_offset, - rotate=m1m2_via_rotate) - - if end_inv < half_length: - end_i_offset = end_inv_offset + \ - self.inv.input_position.scale(1,-1) - M2_end = end_i_offset - vector(0, 0.5 * drc["minwidth_metal2"]) - else: - end_i_offset = end_inv_offset + \ - self.inv.input_position.scale(-1,1) - M2_end = end_i_offset + vector(0, 0.5 * drc["minwidth_metal2"]) - - if start_inv < half_length and end_inv >= half_length: - mid = [half_length * self.inv.width \ - - 0.5 * drc["minwidth_metal2"], M2_start[1]] - self.add_wire(("metal2", "via2", "metal3"), - [M2_start, mid, M2_end]) - else: - self.add_path(("metal2"), [M2_start, M2_end]) - # set the start of next one after current end - start_inv = end_inv - - def add_vddgnd_label(self): - """add vdd and gnd labels""" - for i in range(3): - if i % 2: - self.add_label(text="vdd", - layer="metal1", - offset=[0, i * self.bitcell_height]) - else: - self.add_label(text="gnd", - layer="metal1", - offset=[0, i * self.bitcell_height]) diff --git a/compiler/ms_flop.py b/compiler/ms_flop.py index 035cd00d..cf53b439 100644 --- a/compiler/ms_flop.py +++ b/compiler/ms_flop.py @@ -10,33 +10,19 @@ class ms_flop(design.design): Memory address flip-flop """ - pins = ["din", "dout", "dout_bar", "clk", "vdd", "gnd"] - chars = utils.auto_measure_libcell(pins, "ms_flop", GDS["unit"], layer["boundary"]) - - def __init__(self, name): + pin_names = ["din", "dout", "dout_bar", "clk", "vdd", "gnd"] + (width,height) = utils.get_libcell_size("ms_flop", GDS["unit"], layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "ms_flop", GDS["unit"], layer["boundary"]) + + def __init__(self, name="ms_flop"): design.design.__init__(self, name) - self.width = ms_flop.chars["width"] - self.height = ms_flop.chars["height"] - - self.clk_offset = ms_flop.chars["clk"] - self.din_offset = ms_flop.chars["din"] - self.dout_offset = ms_flop.chars["dout"] - self.dout_bar_offset = ms_flop.chars["dout_bar"] - + self.width = ms_flop.width + self.height = ms_flop.height + self.pin_map = ms_flop.pin_map + def delay(self, slew, load = 0.0): - #import pinv - # use inv to mimic the delay - # din -> mout - #ref = pinv.pinv("reference_inv") - #mid_load = ref.input_load() - #din_t_mout_delay = ref.delay(slew = slew, load = mid_load) - - # mout -> out - #mout_t_out_delay = ref.delay(slew = slew, load = load) - #result = din_t_mout_delay + mout_t_out_delay - - # dont k how to calculate this now, use constant in tech file + # dont know how to calculate this now, use constant in tech file from tech import spice result = self.return_delay(spice["msflop_delay"], spice["msflop_slew"]) return result diff --git a/compiler/ms_flop_array.py b/compiler/ms_flop_array.py index 8599f90e..c9d25c67 100644 --- a/compiler/ms_flop_array.py +++ b/compiler/ms_flop_array.py @@ -12,47 +12,34 @@ class ms_flop_array(design.design): hierdecoder """ - def __init__(self, name, columns, word_size): + def __init__(self, columns, word_size, name=""): self.columns = columns self.word_size = word_size + if name=="": + name = "flop_array_c{0}_w{1}".format(columns,word_size) design.design.__init__(self, name) - debug.info(1, "Creating %s" % self.name) + debug.info(1, "Creating {}".format(self.name)) c = reload(__import__(OPTS.config.ms_flop)) self.mod_ms_flop = getattr(c, OPTS.config.ms_flop) - self.ms_flop_chars = self.mod_ms_flop.chars + self.ms = self.mod_ms_flop("ms_flop") + self.add_mod(self.ms) + + self.width = self.columns * self.ms.width + self.height = self.ms.height + self.words_per_row = self.columns / self.word_size self.create_layout() def create_layout(self): - self.add_modules() - self.setup_layout_constants() self.add_pins() self.create_ms_flop_array() - self.add_labels() + self.add_layout_pins() self.DRC_LVS() - def add_modules(self): - self.ms_flop = self.mod_ms_flop("ms_flop") - self.add_mod(self.ms_flop) - - def setup_layout_constants(self): - self.width = self.columns * self.ms_flop.width - self.height = self.ms_flop.height - self.words_per_row = self.columns / self.word_size - - self.flop_positions = [] - self.vdd_positions = [] - self.gnd_positions = [] - self.clk_positions = [] - self.dout_positions = [] - self.dout_bar_positions = [] - self.din_positions = [] - def add_pins(self): for i in range(self.word_size): self.add_pin("din[{0}]".format(i)) - for i in range(self.word_size): self.add_pin("dout[{0}]".format(i)) self.add_pin("dout_bar[{0}]".format(i)) self.add_pin("clk") @@ -60,97 +47,83 @@ class ms_flop_array(design.design): self.add_pin("gnd") def create_ms_flop_array(self): - for i in range(self.word_size): - name = "Xdff%d" % i - if (i % 2 == 0): - x_off = i * self.ms_flop.width * self.words_per_row - mirror = "None" + self.ms_inst={} + for i in range(0,self.columns,self.words_per_row): + name = "Xdff{0}".format(i) + if (i % 2 == 0 or self.words_per_row>1): + base = vector(i*self.ms.width,0) + mirror = "R0" else: - if (self.words_per_row == 1): - x_off = (i + 1) * self.ms_flop.width - mirror="MY" - else: - x_off = i * self.ms_flop.width * self.words_per_row - self.add_inst(name=name, - mod=self.ms_flop, - offset=[x_off, 0], - mirror=mirror) - self.connect_inst(["din[{0}]".format(i), - "dout[{0}]".format(i), - "dout_bar[{0}]".format(i), + base = vector((i+1)*self.ms.width,0) + mirror = "MY" + self.ms_inst[i]=self.add_inst(name=name, + mod=self.ms, + offset=base, + mirror=mirror) + self.connect_inst(["din[{0}]".format(i/self.words_per_row), + "dout[{0}]".format(i/self.words_per_row), + "dout_bar[{0}]".format(i/self.words_per_row), "clk", "vdd", "gnd"]) - self.flop_positions.append(vector(x_off, 0)) - def add_labels(self): - for i in range(self.word_size): + def add_layout_pins(self): + + for i in range(0,self.columns,self.words_per_row): i_str = "[{0}]".format(i) - if (i % 2 == 0 or self.words_per_row > 1): - base = vector(i * self.ms_flop.width * self.words_per_row, 0) - self.add_label(text="gnd", - layer="metal2", - offset=base + self.ms_flop_chars["gnd"]) - self.add_label(text="din" + i_str, - layer="metal2", - offset=base + self.ms_flop_chars["din"]) - self.add_label(text="dout" + i_str, - layer="metal2", - offset=base + self.ms_flop_chars["dout"]) - self.add_label(text="dout_bar" + i_str, - layer="metal2", - offset=base + self.ms_flop_chars["dout_bar"]) - self.din_positions.append(base + self.ms_flop_chars["din"]) - self.dout_positions.append(base + self.ms_flop_chars["dout"]) - self.dout_bar_positions.append(base + self.ms_flop_chars["dout_bar"]) - self.gnd_positions.append(base + self.ms_flop_chars["gnd"]) - else: - base = vector((i + 1) * self.ms_flop.width, 0) - gnd_offset = base + vector(self.ms_flop_chars["gnd"]).scale(-1,1) - din_offset = base + vector(self.ms_flop_chars["din"]).scale(-1,1) - dout_offset = base + vector(self.ms_flop_chars["dout"]).scale(-1,1) - dout_bar_offset = base + vector(self.ms_flop_chars["dout_bar"]).scale(-1,1) + # Avoid duplicate rails by only doing even columns or last one + for gnd_pin in self.ms_inst[i].get_pins("gnd"): + if gnd_pin.layer!="metal2": + continue + if i%2==0 or i+self.words_per_row>=self.columns: + self.add_layout_pin(text="gnd", + layer="metal2", + offset=gnd_pin.ll(), + width=gnd_pin.width(), + height=gnd_pin.height()) - self.add_label(text="gnd", - layer="metal2", - offset=gnd_offset) - self.add_label(text="din" + i_str, - layer="metal2", - offset=din_offset) - self.add_label(text="dout" + i_str, - layer="metal2", - offset=dout_offset) - self.add_label(text="dout_bar" + i_str, - layer="metal2", - offset=dout_bar_offset) + din_pin = self.ms_inst[i].get_pin("din") + self.add_layout_pin(text="din"+i_str, + layer="metal2", + offset=din_pin.ll(), + width=din_pin.width(), + height=din_pin.height()) - self.gnd_positions.append(gnd_offset) - self.din_positions.append(din_offset) - self.dout_positions.append(dout_offset) - self.dout_bar_positions.append(dout_bar_offset) + dout_pin = self.ms_inst[i].get_pin("dout") + self.add_layout_pin(text="dout"+i_str, + layer="metal2", + offset=dout_pin.ll(), + width=dout_pin.width(), + height=dout_pin.height()) + doutbar_pin = self.ms_inst[i].get_pin("dout_bar") + self.add_layout_pin(text="dout_bar"+i_str, + layer="metal2", + offset=doutbar_pin.ll(), + width=doutbar_pin.width(), + height=doutbar_pin.height()) + + # Continous "clk" rail along with label. - self.add_rect(layer="metal1", - offset=[0, self.ms_flop_chars["clk"][1]], - width=self.width, - height=-drc["minwidth_metal1"]) - self.add_label(text="clk", - layer="metal1", - offset=self.ms_flop_chars["clk"]) - self.clk_positions.append(vector(self.ms_flop_chars["clk"])) + self.add_layout_pin(text="clk", + layer="metal1", + offset=self.ms_inst[0].get_pin("clk").ll().scale(0,1), + width=self.width, + height=drc["minwidth_metal1"]) + # Continous "Vdd" rail along with label. - self.add_rect(layer="metal1", - offset=[0, self.ms_flop_chars["vdd"][1] - 0.5 * drc["minwidth_metal1"]], - width=self.width, - height=drc["minwidth_metal1"]) - self.add_label(text="vdd", - layer="metal1", - offset=vector(self.ms_flop_chars["vdd"]).scale(0, 1)) - self.vdd_positions.append(vector(self.ms_flop_chars["vdd"]).scale(0, 1)) + for vdd_pin in self.ms_inst[i].get_pins("vdd"): + if vdd_pin.layer!="metal1": + continue + self.add_layout_pin(text="vdd", + layer="metal1", + offset=vdd_pin.ll().scale(0,1), + width=self.width, + height=drc["minwidth_metal1"]) def delay(self, slew, load=0.0): - result = self.ms_flop.delay(slew = slew, - load = load) + result = self.ms.delay(slew = slew, + load = load) return result diff --git a/compiler/nand_2.py b/compiler/nand_2.py index 2c742962..898c1a57 100644 --- a/compiler/nand_2.py +++ b/compiler/nand_2.py @@ -21,15 +21,17 @@ class nand_2(design.design): unique_id = 1 - def __init__(self, nmos_width=1, height=bitcell.chars["height"]): + def __init__(self, nmos_width=2*drc["minwidth_tx"], height=bitcell.height): """Constructor : Creates a cell for a simple 2 input nand""" name = "nand2_{0}".format(nand_2.unique_id) nand_2.unique_id += 1 design.design.__init__(self, name) - debug.info(2, "create nand_2 structure {0} with size of {1}".format( - name, nmos_width)) + debug.info(2, "create nand_2 structure {0} with size of {1}".format(name, nmos_width)) - self.nmos_width = nmos_width + self.nmos_size = nmos_width + # FIXME why is this? + self.pmos_size = nmos_width + self.tx_mults = 1 self.height = height self.add_pins() @@ -42,7 +44,6 @@ class nand_2(design.design): def create_layout(self): """ Layout """ - self.determine_sizes() self.create_ptx() self.setup_layout_constants() self.add_rails() @@ -58,14 +59,6 @@ class nand_2(design.design): self.route_pins() self.extend_wells() self.extend_active() - self.setup_layout_offsets() - - # Determine transistor size - def determine_sizes(self): - """ Determine the size of the transistors """ - self.nmos_size = self.nmos_width - self.pmos_size = self.nmos_width - self.tx_mults = 1 # transistors are created here but not yet placed or added as a module def create_ptx(self): @@ -103,23 +96,19 @@ class nand_2(design.design): rail_height = drc["minwidth_metal1"] self.rail_height = rail_height # Relocate the origin - self.gnd_position = vector(0, - 0.5 * drc["minwidth_metal1"]) - self.add_rect(layer="metal1", - offset=self.gnd_position, - width=rail_width, - height=rail_height) - self.add_label(text="gnd", - layer="metal1", - offset=self.gnd_position) + self.gnd_loc = vector(0, - 0.5 * drc["minwidth_metal1"]) + self.add_layout_pin(text="gnd", + layer="metal1", + offset=self.gnd_loc, + width=rail_width, + height=rail_height) - self.vdd_position = vector(0, self.height - 0.5 * drc["minwidth_metal1"]) - self.add_rect(layer="metal1", - offset=self.vdd_position, - width=rail_width, - height=rail_height) - self.add_label(text="vdd", - layer="metal1", - offset=self.vdd_position) + self.vdd_loc = vector(0, self.height - 0.5 * drc["minwidth_metal1"]) + self.add_layout_pin(text="vdd", + layer="metal1", + offset=self.vdd_loc, + width=rail_width, + height=rail_height) def add_ptx(self): """ transistors are added and placed inside the layout """ @@ -297,7 +286,7 @@ class nand_2(design.design): + self.pmos2.active_height + drc["metal1_to_metal1"] + self.pmos2.active_contact.second_layer_width)) - if (self.nmos_width == drc["minwidth_tx"]): + if (self.nmos_size == drc["minwidth_tx"]): yoffset = (self.pmos_position1.y + self.pmos1.poly_positions[0].y + drc["poly_extend_active"] @@ -322,13 +311,11 @@ class nand_2(design.design): - self.poly_contact.height) yoffset += self.poly_contact.via_layer_position.x offset = self.input_position1 = vector(0, yoffset) - self.add_rect(layer="metal1", - offset=offset, - width=input_length, - height=drc["minwidth_metal1"]) - self.add_label(text="A", - layer="metal1", - offset=offset) + self.add_layout_pin(text="A", + layer="metal1", + offset=offset, + width=input_length, + height=drc["minwidth_metal1"]) def route_input_gate_B(self): """ routing for input B """ @@ -338,7 +325,7 @@ class nand_2(design.design): + drc["metal1_to_metal1"] + self.nmos2.active_height + drc["minwidth_metal1"]) - if (self.nmos_width == drc["minwidth_tx"]): + if (self.nmos_size == drc["minwidth_tx"]): yoffset = (self.nmos_position1.y + self.nmos1.poly_positions[0].y + self.nmos1.poly_height @@ -350,11 +337,11 @@ class nand_2(design.design): rotate=90) input_length = self.pmos2.poly_positions[0].x - self.poly_contact.height - self.input_position2 = vector(xoffset - self.poly_contact.width, - yoffset + self.poly_contact.via_layer_position.x) + input_position2 = vector(xoffset - self.poly_contact.width, + yoffset + self.poly_contact.via_layer_position.x) self.add_layout_pin(text="B", layer="metal1", - offset=self.input_position2.scale(0,1), + offset=input_position2.scale(0,1), width=(input_length + self.pmos_position2.x + drc["minwidth_poly"]), height=drc["minwidth_metal1"]) @@ -363,13 +350,13 @@ class nand_2(design.design): yoffset = (self.nmos1.height - 2 * drc["minwidth_metal1"] / 3 + (self.height - self.pmos1.height - self.nmos1.height - drc["minwidth_metal1"]) / 2 ) xoffset = self.drain_position.x - offset = self.output_position = vector(xoffset, yoffset) + offset = vector(xoffset, yoffset) output_length = self.width - xoffset self.add_layout_pin(text="Z", - layer="metal1", - offset=offset, - width=output_length, - height=drc["minwidth_metal1"]) + layer="metal1", + offset=offset, + width=output_length, + height=drc["minwidth_metal1"]) def extend_wells(self): """ Extension of well """ @@ -435,13 +422,6 @@ class nand_2(design.design): width=width, height=self.nmos1.active_height) - def setup_layout_offsets(self): - """ Defining the position of i/o pins for the two input nand gate """ - self.A_position = self.A_position = self.input_position1 - self.B_position = self.B_position = self.input_position2 - self.Z_position = self.Z_position = self.output_position - self.vdd_position = self.vdd_position - self.gnd_position = self.gnd_position def input_load(self): from tech import spice diff --git a/compiler/nand_3.py b/compiler/nand_3.py index a570d3f7..a0b47510 100644 --- a/compiler/nand_3.py +++ b/compiler/nand_3.py @@ -21,14 +21,17 @@ class nand_3(design.design): unique_id = 1 - def __init__(self, nmos_width=1, height=bitcell.chars["height"]): + def __init__(self, nmos_width=3*drc["minwidth_tx"], height=bitcell.height): """Constructor : Creates a cell for a simple 3 input nand""" name = "nand3_{0}".format(nand_3.unique_id) nand_3.unique_id += 1 design.design.__init__(self, name) debug.info(2, "create nand_3 structure {0} with size of {1}".format(name, nmos_width)) - self.nmos_width = nmos_width + self.nmos_size = nmos_width + # FIXME: Why is this?? + self.pmos_size = 2 * nmos_width / 3 + self.tx_mults = 1 self.height = height self.add_pins() @@ -41,7 +44,6 @@ class nand_3(design.design): def create_layout(self): """ create layout """ - self.determine_sizes() self.create_ptx() self.setup_layout_constants() self.add_rails() @@ -58,48 +60,29 @@ class nand_3(design.design): self.extend_active() self.connect_rails() self.route_pins() - self.setup_layout_offsets() - - def determine_sizes(self): - """ Determine the size of the transistors used in this module """ - self.nmos_size = self.nmos_width - self.pmos_size = 2 * self.nmos_width / 3 - self.tx_mults = 1 def create_ptx(self): """ Create ptx but not yet placed""" - self.nmos1 = ptx(width=self.nmos_size, + self.nmos = ptx(width=self.nmos_size, mults=self.tx_mults, tx_type="nmos") - self.add_mod(self.nmos1) - self.nmos2 = ptx(width=self.nmos_size, - mults=self.tx_mults, - tx_type="nmos") - self.add_mod(self.nmos2) - self.nmos3 = ptx(width=self.nmos_size, - mults=self.tx_mults, - tx_type="nmos") - self.add_mod(self.nmos3) + self.add_mod(self.nmos) + self.add_mod(self.nmos) + self.add_mod(self.nmos) - self.pmos1 = ptx(width=self.pmos_size, + self.pmos = ptx(width=self.pmos_size, mults=self.tx_mults, tx_type="pmos") - self.add_mod(self.pmos1) - self.pmos2 = ptx(width=self.pmos_size, - mults=self.tx_mults, - tx_type="pmos") - self.add_mod(self.pmos2) - self.pmos3 = ptx(width=self.pmos_size, - mults=self.tx_mults, - tx_type="pmos") - self.add_mod(self.pmos3) + self.add_mod(self.pmos) + self.add_mod(self.pmos) + self.add_mod(self.pmos) def setup_layout_constants(self): """ setup layout constraints """ - self.well_width = self.nmos1.active_position.x \ - + 3 * self.pmos1.active_width + drc["active_to_body_active"] \ - + drc["well_enclosure_active"] - self.nmos3.active_contact.width \ - + self.pmos1.active_contact.height + drc["minwidth_metal1"] \ + self.well_width = self.nmos.active_position.x \ + + 3 * self.pmos.active_width + drc["active_to_body_active"] \ + + drc["well_enclosure_active"] - self.nmos.active_contact.width \ + + self.pmos.active_contact.height + drc["minwidth_metal1"] \ + (drc["metal1_to_metal1"] - drc["well_enclosure_active"]) self.width = self.width = self.well_width @@ -109,86 +92,86 @@ class nand_3(design.design): self.rail_height = rail_height = drc["minwidth_metal1"] # Relocate the origin - self.gnd_position = vector(0 , - 0.5 * drc["minwidth_metal1"]) + self.gnd_loc = vector(0 , - 0.5 * drc["minwidth_metal1"]) self.add_layout_pin(text="gnd", - layer="metal1", - offset=self.gnd_position, - width=rail_width, - height=rail_height) + layer="metal1", + offset=self.gnd_loc, + width=rail_width, + height=rail_height) - self.vdd_position = vector(0, self.height - 0.5 * drc["minwidth_metal1"]) + self.vdd_loc = vector(0, self.height - 0.5 * drc["minwidth_metal1"]) self.add_layout_pin(text="vdd", - layer="metal1", - offset=self.vdd_position, - width=rail_width, - height=rail_height) + layer="metal1", + offset=self.vdd_loc, + width=rail_width, + height=rail_height) def add_ptx(self): """ transistors are added and placed inside the layout """ # determines the spacing between the edge and nmos (rail to active # metal or poly_to_poly spacing) self.edge_to_nmos = max(drc["metal1_to_metal1"] - - self.nmos1.active_contact_positions[0].y, + - self.nmos.active_contact_positions[0].y, 0.5 * drc["poly_to_poly"] - 0.5 * drc["minwidth_metal1"] - - self.nmos1.poly_positions[0].y) + - self.nmos.poly_positions[0].y) # Extra offset is calculated to make room for B and C contancts - xoffset = (self.nmos1.active_contact.width + xoffset = (self.nmos.active_contact.width + drc["minwidth_metal1"] + (drc["metal1_to_metal1"] - drc["well_enclosure_active"])) # determine the position of the first transistor from the left self.nmos_position1 = vector(xoffset, 0.5 * drc["minwidth_metal1"] + self.edge_to_nmos) - offset = self.nmos_position1 + vector(0,self.nmos1.height) - self.add_inst(name="nmos1", - mod=self.nmos1, + offset = self.nmos_position1 + vector(0,self.nmos.height) + self.add_inst(name="nmos", + mod=self.nmos, offset=offset, mirror="MX") self.connect_inst(["net2", "A", "gnd", "gnd"]) self.nmos_position2 = (self.nmos_position1 - + vector(self.nmos2.active_width,0) - - vector(self.nmos2.active_contact.width,0)) - offset = self.nmos_position2 + vector(0, self.nmos2.height) + + vector(self.nmos.active_width,0) + - vector(self.nmos.active_contact.width,0)) + offset = self.nmos_position2 + vector(0, self.nmos.height) self.add_inst(name="nmos2", - mod=self.nmos2, + mod=self.nmos, offset=offset, mirror="MX") self.connect_inst(["net1", "B", "net2", "gnd"]) - p2tp3 = vector(self.nmos3.active_width - self.nmos3.active_contact.width, - self.nmos3.height) + p2tp3 = vector(self.nmos.active_width - self.nmos.active_contact.width, + self.nmos.height) self.nmos_position3 = self.nmos_position2 + p2tp3 self.add_inst(name="nmos3", - mod=self.nmos3, + mod=self.nmos, offset=self.nmos_position3, mirror="MX") self.connect_inst(["Z", "C", "net1", "gnd"]) # determines the spacing between the edge and pmos self.edge_to_pmos = max(drc["metal1_to_metal1"] - - self.pmos1.active_contact_positions[0].y, + - self.pmos.active_contact_positions[0].y, 0.5 * drc["poly_to_poly"] - 0.5 * drc["minwidth_metal1"] - - self.pmos1.poly_positions[0].y) + - self.pmos.poly_positions[0].y) self.pmos_position1 = vector(self.nmos_position1.x, self.height - 0.5 * drc["minwidth_metal1"] - - self.pmos1.height - self.edge_to_pmos) + - self.pmos.height - self.edge_to_pmos) self.add_inst(name="pmos1", - mod=self.pmos1, + mod=self.pmos, offset=self.pmos_position1) self.connect_inst(["Z", "A", "vdd", "vdd"]) self.pmos_position2 = vector(self.nmos_position2.x, self.pmos_position1.y) self.add_inst(name="pmos2", - mod=self.pmos2, + mod=self.pmos, offset=self.pmos_position2) self.connect_inst(["vdd", "B", "Z", "vdd"]) self.pmos_position3 = vector(self.nmos_position3.x, self.pmos_position1.y) self.add_inst(name="pmos3", - mod=self.pmos3, + mod=self.pmos, offset=self.pmos_position3) self.connect_inst(["Z", "C", "vdd", "vdd"]) @@ -196,20 +179,20 @@ class nand_3(design.design): """ create well contacts """ layer_stack = ("active", "contact", "metal1") - xoffset = (self.nmos_position3.x + self.pmos1.active_position.x - + self.pmos1.active_width + drc["active_to_body_active"]) - yoffset = self.pmos_position1.y + self.pmos1.active_contact_positions[0].y + xoffset = (self.nmos_position3.x + self.pmos.active_position.x + + self.pmos.active_width + drc["active_to_body_active"]) + yoffset = self.pmos_position1.y + self.pmos.active_contact_positions[0].y self.nwell_contact_position = vector(xoffset, yoffset) - self.nwell_contact=self.add_contact(layer_stack,self.nwell_contact_position,(1,self.pmos1.num_of_tacts)) + self.nwell_contact=self.add_contact(layer_stack,self.nwell_contact_position,(1,self.pmos.num_of_tacts)) - xoffset = self.nmos_position3.x + (self.nmos1.active_position.x - + self.nmos1.active_width + xoffset = self.nmos_position3.x + (self.nmos.active_position.x + + self.nmos.active_width + drc["active_to_body_active"]) - yoffset = self.nmos_position1.y + (self.nmos1.height - - self.nmos1.active_contact_positions[0].y - - self.nmos1.active_contact.height) + yoffset = self.nmos_position1.y + (self.nmos.height + - self.nmos.active_contact_positions[0].y + - self.nmos.active_contact.height) self.pwell_contact_position = vector(xoffset, yoffset) - self.pwell_contact=self.add_contact(layer_stack,self.pwell_contact_position,(1,self.nmos1.num_of_tacts)) + self.pwell_contact=self.add_contact(layer_stack,self.pwell_contact_position,(1,self.nmos.num_of_tacts)) def connect_well_contacts(self): """ Connect well contacts to vdd and gnd rail """ @@ -223,7 +206,7 @@ class nand_3(design.design): width=drc["minwidth_metal1"], height=well_tap_length) - well_tap_length = self.nmos1.active_height + well_tap_length = self.nmos.active_height offset = vector(self.pwell_contact_position + self.pwell_contact.second_layer_position - self.pwell_contact.first_layer_position).scale(1,0) @@ -234,23 +217,23 @@ class nand_3(design.design): def connect_rails(self): """ Connect transistor pmos drains to vdd and nmos drains to gnd rail """ - correct = vector(self.pmos1.active_contact.width - drc["minwidth_metal1"], + correct = vector(self.pmos.active_contact.width - drc["minwidth_metal1"], 0).scale(0.5,0) - poffset = self.pmos_position1 + self.pmos1.active_contact_positions[0] + correct + poffset = self.pmos_position1 + self.pmos.active_contact_positions[0] + correct temp_height = self.height - poffset.y self.add_rect(layer="metal1", offset=poffset, width=drc["minwidth_metal1"], height=temp_height) - poffset = [self.pmos_position3.x + self.pmos3.active_contact_positions[0].x + correct.x, + poffset = [self.pmos_position3.x + self.pmos.active_contact_positions[0].x + correct.x, poffset.y] self.add_rect(layer="metal1", offset=poffset, width=drc["minwidth_metal1"], height=temp_height) - poffset = self.nmos_position1 + self.nmos1.active_contact_positions[0] + correct + poffset = self.nmos_position1 + self.nmos.active_contact_positions[0] + correct self.add_rect(layer="metal1", offset=poffset.scale(1,0), width=drc["minwidth_metal1"], @@ -263,57 +246,57 @@ class nand_3(design.design): def connect_poly(self): """ Connect poly """ - yoffset_nmos1 = (self.nmos_position1.y + self.nmos1.poly_positions[0].y - + self.nmos1.poly_height) - poly_length = (self.pmos_position1.y + self.pmos1.poly_positions[0].y - - yoffset_nmos1 + drc["minwidth_poly"]) + yoffset_nmos = (self.nmos_position1.y + self.nmos.poly_positions[0].y + + self.nmos.poly_height) + poly_length = (self.pmos_position1.y + self.pmos.poly_positions[0].y + - yoffset_nmos + drc["minwidth_poly"]) - offset = vector(self.nmos_position1.x + self.nmos1.poly_positions[0].x, - yoffset_nmos1 - drc["minwidth_poly"]) + offset = vector(self.nmos_position1.x + self.nmos.poly_positions[0].x, + yoffset_nmos - drc["minwidth_poly"]) self.add_rect(layer="poly", offset=offset, width=drc["minwidth_poly"], height=poly_length) self.add_rect(layer="poly", - offset=[offset.x + self.pmos1.active_contact.width + 2 * drc["minwidth_poly"], + offset=[offset.x + self.pmos.active_contact.width + 2 * drc["minwidth_poly"], offset.y], width=drc["minwidth_poly"], height=poly_length) self.add_rect(layer="poly", - offset=[offset.x + 2 * self.pmos1.active_contact.width + 4 * drc["minwidth_poly"], + offset=[offset.x + 2 * self.pmos.active_contact.width + 4 * drc["minwidth_poly"], offset.y], width=drc["minwidth_poly"], height=poly_length) def connect_drains(self): """ Connect pmos and nmos drains. The output will be routed to this connection point. """ - yoffset = drc["minwidth_metal1"] + (self.nmos1.active_contact_positions[0].y + yoffset = drc["minwidth_metal1"] + (self.nmos.active_contact_positions[0].y - drc["well_enclosure_active"] + drc["metal1_to_metal1"]) drain_length = (self.height - yoffset + 0.5 * drc["minwidth_metal1"] - - (self.pmos1.height - self.pmos1.active_contact_positions[0].y)) + - (self.pmos.height - self.pmos.active_contact_positions[0].y)) layer_stack = ("metal1", "via1", "metal2") - for position in self.pmos1.active_contact_positions[1:][::2]: - diff_active_via = self.pmos2.active_contact.width - self.m1m2_via.second_layer_width - offset = (self.pmos_position2 + self.pmos2.active_contact_positions[0] + for position in self.pmos.active_contact_positions[1:][::2]: + diff_active_via = self.pmos.active_contact.width - self.m1m2_via.second_layer_width + offset = (self.pmos_position2 + self.pmos.active_contact_positions[0] + vector(diff_active_via / 2,0)) self.add_via(layer_stack,offset) - width = (2 * self.pmos3.active_width - - self.pmos3.active_contact.width - - (self.pmos2.active_contact.width + width = (2 * self.pmos.active_width + - self.pmos.active_contact.width + - (self.pmos.active_contact.width - self.m1m2_via.second_layer_width)) self.add_rect(layer="metal2", offset=offset, width=width, height=self.m1m2_via.second_layer_width) - xoffset = (self.pmos_position3.x + self.pmos3.active_contact_positions[1].x + xoffset = (self.pmos_position3.x + self.pmos.active_contact_positions[1].x + diff_active_via / 2) self.add_via(layer_stack,[xoffset,offset.y]) - xoffset = (self.nmos_position3.x + self.nmos3.active_position.x - + self.nmos3.active_width - self.nmos3.active_contact.width / 2) + xoffset = (self.nmos_position3.x + self.nmos.active_position.x + + self.nmos.active_width - self.nmos.active_contact.width / 2) self.drain_position = vector(xoffset, drc["minwidth_metal1"] + drc["metal1_to_metal1"]) length = self.height - 2 * (drc["minwidth_metal1"] + drc["metal1_to_metal1"]) @@ -332,140 +315,72 @@ class nand_3(design.design): def route_input_gate_A(self): """ routing for input A """ - offset = (self.pmos_position1 - + self.pmos1.poly_positions[0] - - vector(drc["minwidth_poly"] / 2, self.poly_contact.width)) - self.add_contact(layers=("poly", "contact", "metal1"), - offset=offset, - rotate=90) - self.add_rect(layer="poly", - offset=offset + vector(drc["minwidth_poly"] / 2,0), - width=-(self.poly_contact.first_layer_position.y + drc["minwidth_poly"]), - height=self.poly_contact.first_layer_width) + offset = self.pmos_position1 + self.pmos.poly_positions[0] - vector(0,self.poly_contact.height) + via_offset = offset + self.poly_contact.first_layer_position.scale(-1,0) - vector(self.poly_contact.first_layer_width,0) + vector(drc["minwidth_poly"],0) + + self.add_contact(layers=("poly", "contact", "metal1"), + offset=via_offset) - offset = vector(offset.x, - self.pmos_position1.y + self.pmos1.poly_positions[0].y) self.add_layout_pin(text="A", - layer="metal1", - offset=offset, - width=-offset.x, - height=-drc["minwidth_metal1"]) - self.A_position = vector(0, offset.y - drc["minwidth_metal1"]) + layer="metal1", + offset=offset.scale(0,1), + width=offset.x, + height=drc["minwidth_metal1"]) def route_input_gate_B(self): """ routing for input B """ - xoffset = self.pmos2.poly_positions[0].x \ - + self.pmos_position2.x - drc["minwidth_poly"] - yoffset = self.nmos_position1.y + self.nmos1.height \ - - drc["well_enclosure_active"] + (self.nmos1.active_contact.height \ - - self.nmos1.active_height) / 2 \ - + drc["metal1_to_metal1"] + + offset = self.pmos_position2 + self.pmos.poly_positions[0] - vector(0,self.poly_contact.height+1*(self.m1m2_via.width+drc["metal1_to_metal1"])) + via_offset = offset + self.poly_contact.first_layer_position.scale(-1,0) - vector(self.poly_contact.first_layer_width,0) + vector(drc["minwidth_poly"],0) + self.add_contact(layers=("poly", "contact", "metal1"), - offset=[xoffset,yoffset]) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=[xoffset,yoffset]) + offset=via_offset) - xoffset = self.pmos2.poly_positions[0].x + self.pmos_position2.x \ - - drc["minwidth_poly"] + self.m1m2_via.width - length = -xoffset + self.m1m2_via.width - self.add_rect(layer="metal2", - offset=[xoffset, yoffset], - width=length, - height=-drc["minwidth_metal2"]) - - self.B_position = vector(0, yoffset - drc["minwidth_metal1"]) - self.add_label(text="B", - layer="metal1", - offset=self.B_position) - - xoffset = self.pmos_position1.x + self.pmos1.active_position.x \ - - drc["metal1_to_metal1"] + (self.pmos1.active_contact.width \ - - self.m1m2_via.second_layer_width) / 2 - self.add_via(layers=("metal1", "via1", "metal2"), - offset=[xoffset,yoffset - drc["minwidth_metal2"]], - rotate=90) - self.add_rect(layer="metal1", - offset=[xoffset, yoffset], - width=-xoffset, - height=-drc["minwidth_metal1"]) + self.add_layout_pin(text="B", + layer="metal1", + offset=offset.scale(0,1), + width=offset.x, + height=drc["minwidth_metal1"]) + def route_input_gate_C(self): - """ routing for input A """ - xoffset = self.pmos3.poly_positions[0].x \ - + self.pmos_position3.x - drc["minwidth_poly"] - yoffset = self.nmos_position1.y + self.nmos1.height \ - - drc["well_enclosure_active"] + (self.nmos1.active_contact.height \ - - self.nmos1.active_height) / 2 + drc["metal1_to_metal1"] + """ routing for input C """ + offset = self.pmos_position3 + self.pmos.poly_positions[0] - vector(0,self.poly_contact.height+2*(self.m1m2_via.width+drc["metal1_to_metal1"])) + via_offset = offset + self.poly_contact.first_layer_position.scale(-1,0) - vector(self.poly_contact.first_layer_width,0) + vector(drc["minwidth_poly"],0) self.add_contact(layers=("poly", "contact", "metal1"), - offset=[xoffset,yoffset]) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=[xoffset,yoffset]) + offset=via_offset) - xoffset = self.pmos3.poly_positions[0].x \ - + self.pmos_position3.x - drc["minwidth_poly"] \ - + self.m1m2_via.width - length = -xoffset + self.m1m2_via.width - self.add_rect(layer="metal2", - offset=[xoffset, - yoffset - drc["minwidth_metal2"] - drc["metal2_to_metal2"]], - width=length, - height=-drc["minwidth_metal2"]) - # FIXME: Convert to add_layout_pin? - self.add_rect(layer="metal2", - offset=[xoffset - self.m1m2_via.width, - yoffset], - width=self.m1m2_via.width, - height=-drc["minwidth_metal2"] - drc["metal2_to_metal2"]) - self.C_position = vector(0, - self.B_position.y - drc["metal2_to_metal2"] - drc["minwidth_metal1"] \ - - (self.m1m2_via.second_layer_width - - self.m1m2_via.first_layer_width)) - self.add_label(text="C", - layer="metal1", - offset=self.C_position) - - xoffset = self.pmos_position1.x + self.pmos1.active_position.x \ - - drc["metal1_to_metal1"] + (self.pmos1.active_contact.width \ - - self.m1m2_via.second_layer_width) / 2 - self.add_via(layers=("metal1", "via1", "metal2"), - offset=[xoffset, - yoffset - 2 * drc["minwidth_metal2"] - self.m1m2_via.width], - rotate=90) - self.add_rect(layer="metal1", - offset=[xoffset, - yoffset - 2 * drc["minwidth_metal2"]], - width=-xoffset, - height=-drc["minwidth_metal1"]) + self.add_layout_pin(text="C", + layer="metal1", + offset=offset.scale(0,1), + width=offset.x, + height=drc["minwidth_metal1"]) def route_output(self): """ routing for output Z """ - xoffset = self.nmos_position3.x + self.nmos3.active_position.x \ - + self.nmos3.active_width - self.nmos3.active_contact.width / 2 - yoffset = (self.nmos1.height + (self.height - drc["minwidth_metal1"] - - self.pmos1.height - self.nmos1.height) / 2 + xoffset = self.nmos_position3.x + self.nmos.active_position.x \ + + self.nmos.active_width - self.nmos.active_contact.width / 2 + yoffset = (self.nmos.height + (self.height - drc["minwidth_metal1"] + - self.pmos.height - self.nmos.height) / 2 - (drc["minwidth_metal1"] / 2)) - # FIXME Convert to add_layout_pin? - self.add_rect(layer="metal1", - offset=[xoffset, yoffset], - width=self.well_width - xoffset, - height=drc["minwidth_metal1"]) + self.add_layout_pin(text="Z", + layer="metal1", + offset=vector(xoffset, yoffset), + width=self.well_width - xoffset, + height=drc["minwidth_metal1"]) - self.Z_position = vector(self.well_width, yoffset) - self.add_label(text="Z", - layer="metal1", - offset=self.Z_position) def extend_wells(self): """ extension of well """ - middle_point = self.nmos_position1.y + self.nmos1.pwell_position.y \ - + self.nmos1.well_height + (self.pmos_position1.y - + self.pmos1.nwell_position.y + middle_point = self.nmos_position1.y + self.nmos.pwell_position.y \ + + self.nmos.well_height + (self.pmos_position1.y + + self.pmos.nwell_position.y - self.nmos_position1.y - - self.nmos1.pwell_position.y - - self.nmos1.well_height) / 2 + - self.nmos.pwell_position.y + - self.nmos.well_height) / 2 offset = self.nwell_position = vector(0, middle_point) self.nwell_height = self.height - middle_point self.add_rect(layer="nwell", @@ -489,45 +404,37 @@ class nand_3(design.design): def extend_active(self): """ extension of active region """ - self.active_width = self.pmos1.active_width \ - + drc["active_to_body_active"] + self.pmos1.active_contact.width - offset = (self.pmos1.active_position+self.pmos_position3.scale(1,0) + self.active_width = self.pmos.active_width \ + + drc["active_to_body_active"] + self.pmos.active_contact.width + offset = (self.pmos.active_position+self.pmos_position3.scale(1,0) + self.pmos_position1.scale(0,1)) self.add_rect(layer="active", offset=offset, width=self.active_width, - height=self.pmos1.active_height) + height=self.pmos.active_height) - offset = offset + vector(self.pmos1.active_width,0) - width = self.active_width - self.pmos1.active_width + offset = offset + vector(self.pmos.active_width,0) + width = self.active_width - self.pmos.active_width self.add_rect(layer="nimplant", offset=offset, width=width, - height=self.pmos1.active_height) + height=self.pmos.active_height) - offset = [self.nmos_position3.x + self.nmos1.active_position.x, - self.nmos_position1.y + self.nmos1.height - - self.nmos1.active_position.y - self.nmos1.active_height] + offset = [self.nmos_position3.x + self.nmos.active_position.x, + self.nmos_position1.y + self.nmos.height + - self.nmos.active_position.y - self.nmos.active_height] self.add_rect(layer="active", offset=offset, width=self.active_width, - height=self.nmos1.active_height) + height=self.nmos.active_height) - offset = offset + vector(self.nmos1.active_width,0) - width = self.active_width - self.nmos1.active_width + offset = offset + vector(self.nmos.active_width,0) + width = self.active_width - self.nmos.active_width self.add_rect(layer="pimplant", offset=offset, width=width, - height=self.nmos1.active_height) + height=self.nmos.active_height) - def setup_layout_offsets(self): - """ Defining the position of i/o pins for the three input nand gate """ - self.A_position = self.A_position - self.B_position = self.B_position - self.C_position = self.C_position - self.Z_position = self.Z_position - self.vdd_position = self.vdd_position - self.gnd_position = self.gnd_position def input_load(self): return ((self.nmos_size+self.pmos_size)/parameter["min_tx_size"])*spice["min_tx_gate_c"] diff --git a/compiler/nor_2.py b/compiler/nor_2.py index 454cd9c4..13d47ffa 100644 --- a/compiler/nor_2.py +++ b/compiler/nor_2.py @@ -20,7 +20,7 @@ class nor_2(design.design): unique_id = 1 - def __init__(self, nmos_width=1, height=bitcell.chars["height"]): + def __init__(self, nmos_width=drc["minwidth_tx"], height=bitcell.height): """Constructor : Creates a cell for a simple 2 input nor""" name = "nor2_{0}".format(nor_2.unique_id) nor_2.unique_id += 1 @@ -47,9 +47,9 @@ class nor_2(design.design): # These aren't for instantiating, but we use them to get the dimensions self.nwell_contact = contact.contact(layer_stack=("active", "contact", "metal1"), - dimensions=(1, self.pmos1.num_of_tacts)) + dimensions=(1, self.pmos1.num_of_tacts)) self.pwell_contact = contact.contact(layer_stack=("active", "contact", "metal1"), - dimensions=(1, self.nmos1.num_of_tacts)) + dimensions=(1, self.nmos1.num_of_tacts)) self.setup_layout_constants() self.add_rails() @@ -81,12 +81,12 @@ class nor_2(design.design): 0.5 * gate_to_gate - test_nmos.poly_positions[0].y) edge_to_pmos = max(drc["metal1_to_metal1"] - test_pmos.active_contact_positions[0].y, 0.5 * gate_to_gate - test_pmos.poly_positions[0].y) - route_margin = .5 * (self.poly_contact.second_layer_width - + drc["minwidth_metal1"]) + drc["metal1_to_metal1"] - pmos_position = vector(0, self.height - 0.5 * drc["minwidth_metal1"] - - edge_to_pmos - test_pmos.height) - nmos_position = vector(0, 0.5 * drc["minwidth_metal1"] + edge_to_nmos) - leftspace = (0.7 * (pmos_position.y - nmos_position.y) + route_margin = 0.5 * (self.poly_contact.second_layer_width + + drc["minwidth_metal1"]) + drc["metal1_to_metal1"] + pmos_loc = vector(0, self.height - 0.5 * drc["minwidth_metal1"] + - edge_to_pmos - test_pmos.height) + nmos_loc = vector(0, 0.5 * drc["minwidth_metal1"] + edge_to_nmos) + leftspace = (0.7 * (pmos_loc.y - nmos_loc.y) - 3 * route_margin - 2 * drc["metal1_to_metal1"]) if leftspace >= 0: break @@ -126,28 +126,26 @@ class nor_2(design.design): half_gate_to_gate - self.nmos1.poly_positions[0].y) # determine the position of the first transistor from the left - self.nmos_position1 = vector(0, - 0.5 * drc["minwidth_metal1"] + edge_to_nmos) - offset = self.nmos_position1 + vector(0,self.nmos1.height) + self.nmos_loc1 = vector(0, 0.5 * drc["minwidth_metal1"] + edge_to_nmos) + offset = self.nmos_loc1 + vector(0,self.nmos1.height) x = vector(self.nmos2.active_width - self.nmos2.active_contact.width, 0) - self.nmos_position2 = x + self.nmos_position1.scale(0,1) + self.nmos_loc2 = x + self.nmos_loc1.scale(0,1) # determines the spacing between the edge and pmos edge_to_pmos = max(drc["metal1_to_metal1"] - self.pmos1.active_contact_positions[0].y, half_gate_to_gate - self.pmos1.poly_positions[0].y) - self.pmos_position1 = vector(0, - self.height - 0.5 * drc["minwidth_metal1"] - - edge_to_pmos - self.pmos1.height) - self.pmos_position2 = self.pmos_position1 + vector(self.pmos1.width,0) + self.pmos_loc1 = vector(0, self.height - 0.5 * drc["minwidth_metal1"] + - edge_to_pmos - self.pmos1.height) + self.pmos_loc2 = self.pmos_loc1 + vector(self.pmos1.width,0) - self.well_width = max(self.pmos_position2.x + self.pmos2.active_position.x - + self.pmos2.active_width - + drc["active_to_body_active"] + self.nwell_contact.width - + drc["well_enclosure_active"], - self.nmos_position2.x + self.nmos2.active_position.x - + self.nmos2.active_width - + drc["active_to_body_active"] + drc["well_enclosure_active"]) + self.well_width = max(self.pmos_loc2.x + self.pmos2.active_position.x + + self.pmos2.active_width + + drc["active_to_body_active"] + self.nwell_contact.width + + drc["well_enclosure_active"], + self.nmos_loc2.x + self.nmos2.active_position.x + + self.nmos2.active_width + + drc["active_to_body_active"] + drc["well_enclosure_active"]) self.width = self.well_width def add_rails(self): @@ -155,43 +153,43 @@ class nor_2(design.design): rail_height = drc["minwidth_metal1"] self.rail_height = rail_height # Relocate the origin - self.gnd_position = vector(0, - 0.5 * drc["minwidth_metal1"]) + self.gnd_loc = vector(0, - 0.5 * drc["minwidth_metal1"]) self.add_layout_pin(text="gnd", layer="metal1", - offset=self.gnd_position, + offset=self.gnd_loc, width=rail_width, height=rail_height) - self.vdd_position = self.gnd_position + vector(0, self.height) + self.vdd_loc = self.gnd_loc + vector(0, self.height) self.add_layout_pin(text="vdd", - layer="metal1", - offset=self.vdd_position, - width=rail_width, - height=rail_height) + layer="metal1", + offset=self.vdd_loc, + width=rail_width, + height=rail_height) def add_ptx(self): """ transistors are placed in the layout""" - offset = self.nmos_position1 + vector(0, self.nmos1.height) + offset = self.nmos_loc1 + vector(0, self.nmos1.height) self.add_inst(name="nmos1", mod=self.nmos1, offset=offset, mirror="MX") self.connect_inst(["Z", "A", "gnd", "gnd"]) - offset = self.nmos_position2 + vector(0, self.nmos2.height) + offset = self.nmos_loc2 + vector(0, self.nmos2.height) self.add_inst(name="nmos2", mod=self.nmos2, offset=offset, mirror="MX") self.connect_inst(["Z", "B", "gnd", "gnd"]) - offset = self.pmos_position1 + offset = self.pmos_loc1 self.add_inst(name="pmos1", mod=self.pmos1, offset=offset) self.connect_inst(["vdd", "A", "net1", "vdd"]) - offset = self.pmos_position2 + offset = self.pmos_loc2 self.add_inst(name="pmos2", mod=self.pmos2, offset=offset) @@ -200,48 +198,48 @@ class nor_2(design.design): def add_well_contacts(self): layer_stack = ("active", "contact", "metal1") - xoffset = (self.pmos_position2.x + self.pmos1.active_position.x + xoffset = (self.pmos_loc2.x + self.pmos1.active_position.x + self.pmos1.active_width + drc["active_to_body_active"]) - yoffset = (self.pmos_position1.y + yoffset = (self.pmos_loc1.y + self.pmos1.active_contact_positions[0].y) - self.nwell_contact_position = vector(xoffset, yoffset) - self.nwell_contact=self.add_contact(layer_stack,self.nwell_contact_position,(1,self.pmos1.num_of_tacts)) + self.nwell_contact_loc = vector(xoffset, yoffset) + self.nwell_contact=self.add_contact(layer_stack,self.nwell_contact_loc,(1,self.pmos1.num_of_tacts)) - xoffset = self.nmos_position2.x + (self.nmos1.active_position.x - + self.nmos1.active_width - + drc["active_to_body_active"]) - yoffset = (self.nmos_position1.y + self.nmos1.height - - self.nmos1.active_contact_positions[0].y - - self.nmos1.active_contact.height) - self.pwell_contact_position = vector(xoffset, yoffset) - self.pwell_contact=self.add_contact(layer_stack,self.pwell_contact_position,(1,self.nmos1.num_of_tacts)) + xoffset = self.nmos_loc2.x + (self.nmos1.active_position.x + + self.nmos1.active_width + + drc["active_to_body_active"]) + yoffset = (self.nmos_loc1.y + self.nmos1.height + - self.nmos1.active_contact_positions[0].y + - self.nmos1.active_contact.height) + self.pwell_contact_loc = vector(xoffset, yoffset) + self.pwell_contact=self.add_contact(layer_stack,self.pwell_contact_loc,(1,self.nmos1.num_of_tacts)) def route(self): self.route_pins() self.connect_well_contacts() - M1_track = (self.B_position.y + drc["metal1_to_metal1"] - + .5 * (self.poly_contact.second_layer_width - + drc["minwidth_metal1"])) + M1_track = (self.B_loc.y + drc["metal1_to_metal1"] + + .5 * (self.poly_contact.second_layer_width + + drc["minwidth_metal1"])) self.connect_tx(M1_track) self.connect_poly() def connect_well_contacts(self): """ Connect well contacts to vdd and gnd rail """ - well_tap_length = self.height - self.nwell_contact_position.y - xoffset = (self.nwell_contact_position.x - + self.nwell_contact.second_layer_position.x - - self.nwell_contact.first_layer_position.x) - offset = [xoffset, self.nwell_contact_position.y] + well_tap_length = self.height - self.nwell_contact_loc.y + xoffset = (self.nwell_contact_loc.x + + self.nwell_contact.second_layer_position.x + - self.nwell_contact.first_layer_position.x) + offset = [xoffset, self.nwell_contact_loc.y] self.add_rect(layer="metal1", offset=offset, width=drc["minwidth_metal1"], height=well_tap_length) - - offset = (self.pwell_contact_position.scale(1,0) - + self.pwell_contact.second_layer_position.scale(1,0) - - self.pwell_contact.first_layer_position.scale(1,0)) - well_tap_length = self.pwell_contact_position.y + + offset = (self.pwell_contact_loc.scale(1,0) + + self.pwell_contact.second_layer_position.scale(1,0) + - self.pwell_contact.first_layer_position.scale(1,0)) + well_tap_length = self.pwell_contact_loc.y self.add_rect(layer="metal1", offset=offset, width=drc["minwidth_metal1"], @@ -251,11 +249,11 @@ class nor_2(design.design): """Connect transistor pmos drains to vdd and nmos drains to gnd rail""" # the first pmos drain to Vdd for i in range(len(self.pmos1.active_contact_positions)): - contact_pos = self.pmos_position1 + self.pmos1.active_contact_positions[i] + contact_pos = self.pmos_loc1 + self.pmos1.active_contact_positions[i] if i % 2 == 0: correct = self.pmos1.active_contact.second_layer_position.scale(1,0) drain_posistion = contact_pos + correct - height = self.vdd_position.y - drain_posistion.y + height = self.vdd_loc.y - drain_posistion.y self.add_rect(layer="metal1", offset=drain_posistion, width=drc["minwidth_metal1"], @@ -263,36 +261,36 @@ class nor_2(design.design): else: # source to pmos2 correct = (self.pmos1.active_contact.second_layer_position.scale(1,0) - + vector(self.pmos1.active_contact.second_layer_width, - 0).scale(.5,0)) - source_position = contact_pos + correct - mid = [self.pmos_position2.x, M1_track] - self.add_path("metal1", [source_position, mid]) + + vector(self.pmos1.active_contact.second_layer_width, + 0).scale(.5,0)) + source_loc = contact_pos + correct + mid = [self.pmos_loc2.x, M1_track] + self.add_path("metal1", [source_loc, mid]) # the second pmos for i in range(len(self.pmos2.active_contact_positions)): if i % 2 == 0: # source to pmos2 - pmos_active =self.pmos_position2+self.pmos2.active_contact_positions[i] + pmos_active =self.pmos_loc2+self.pmos2.active_contact_positions[i] correct= (self.pmos2.active_contact.second_layer_position.scale(1,0) - + vector(0.5 * self.pmos2.active_contact.second_layer_width,0)) - source_position = pmos_active + correct - mid = [self.pmos_position2.x, M1_track] - self.add_path("metal1", [source_position, mid]) + + vector(0.5 * self.pmos2.active_contact.second_layer_width,0)) + source_loc = pmos_active + correct + mid = [self.pmos_loc2.x, M1_track] + self.add_path("metal1", [source_loc, mid]) # two nmos source to gnd - source_posistion1 = (self.nmos_position1 - + self.nmos1.active_contact_positions[0] - + self.nmos1.active_contact.second_layer_position.scale(1,0)) - height = self.gnd_position.y - source_posistion1.y + source_posistion1 = (self.nmos_loc1 + + self.nmos1.active_contact_positions[0] + + self.nmos1.active_contact.second_layer_position.scale(1,0)) + height = self.gnd_loc.y - source_posistion1.y self.add_rect(layer="metal1", offset=source_posistion1, width=drc["minwidth_metal1"], height=height) - source_posistion2 = (self.nmos_position2 - + self.nmos2.active_contact_positions[1] - + self.nmos2.active_contact.second_layer_position.scale(1,0)) - height = self.gnd_position.y - source_posistion2.y + source_posistion2 = (self.nmos_loc2 + + self.nmos2.active_contact_positions[1] + + self.nmos2.active_contact.second_layer_position.scale(1,0)) + height = self.gnd_loc.y - source_posistion2.y self.add_rect(layer="metal1", offset=source_posistion2, width=drc["minwidth_metal1"], @@ -301,29 +299,29 @@ class nor_2(design.design): def connect_poly(self): """connect connect poly between nmos and pmos""" # connect pmos1 poly - nmos_gate = (self.nmos_position1 + nmos_gate = (self.nmos_loc1 + self.nmos1.poly_positions[0] + vector(0.5 * drc["minwidth_poly"], 0)) for i in range(len(self.pmos1.poly_positions)): - pmos_gate = (self.pmos_position1 - + self.pmos1.poly_positions[i] - + vector(0.5 * drc["minwidth_poly"], 0)) + pmos_gate = (self.pmos_loc1 + + self.pmos1.poly_positions[i] + + vector(0.5 * drc["minwidth_poly"], 0)) mid1 = [pmos_gate.x, pmos_gate.y - drc["poly_to_active"]] self.add_path("poly", [nmos_gate, mid1, pmos_gate]) # connect pmos2 poly - nmos_gate = vector(self.nmos_position2[0] - + self.nmos2.poly_positions[0].x - + 0.5 * drc["minwidth_poly"], - self.nmos_position1.y - + self.nmos1.poly_positions[0].y) + nmos_gate = vector(self.nmos_loc2[0] + + self.nmos2.poly_positions[0].x + + 0.5 * drc["minwidth_poly"], + self.nmos_loc1.y + + self.nmos1.poly_positions[0].y) for i in range(len(self.pmos2.poly_positions)): - pmos_gate = (self.pmos_position2 - + self.pmos2.poly_positions[i] - + vector(0.5 * drc["minwidth_poly"], 0)) + pmos_gate = (self.pmos_loc2 + + self.pmos2.poly_positions[i] + + vector(0.5 * drc["minwidth_poly"], 0)) mid1 = vector(pmos_gate.x, nmos_gate.y + self.nmos2.height - + drc["poly_to_active"]) + + drc["poly_to_active"]) self.add_path("poly", [nmos_gate, mid1, pmos_gate]) def route_pins(self): @@ -337,41 +335,39 @@ class nor_2(design.design): def route_input_A(self): """create input A layout""" xoffset = self.nmos1.poly_positions[0].x - yoffset = self.nmos_position1.y + self.nmos1.height \ - + 0.3 * (self.pmos_position1.y - self.nmos_position1.y \ + yoffset = self.nmos_loc1.y + self.nmos1.height \ + + 0.3 * (self.pmos_loc1.y - self.nmos_loc1.y \ - self.nmos1.height) - self.A_position = vector(xoffset, yoffset) + self.A_loc = vector(xoffset, yoffset) # gate input - offset = self.A_position - vector(0, 0.5 * self.poly_contact.width) + offset = self.A_loc - vector(0, 0.5 * self.poly_contact.width) self.add_contact(layers=("poly", "contact", "metal1"), offset=offset, rotate=90) # connect gate input to tx gate - offset = self.A_position - vector(self.poly_contact.first_layer_position.y, - 0.5 * self.poly_contact.width) + offset = self.A_loc - vector(self.poly_contact.first_layer_position.y, + 0.5 * self.poly_contact.width) self.add_rect(layer="poly", offset=offset, width=self.poly_contact.first_layer_position.y + drc["minwidth_poly"], height=self.poly_contact.first_layer_width) # extend the metal to the boundary of the cell - input_length = self.A_position.x - offset = [0, self.A_position.y - 0.5 * drc["minwidth_metal1"]] + input_length = self.A_loc.x + offset = [0, self.A_loc.y - 0.5 * drc["minwidth_metal1"]] self.add_layout_pin(text="A", - layer="metal1", - offset=offset, - width=input_length, - height=drc["minwidth_metal1"]) + layer="metal1", + offset=offset, + width=input_length, + height=drc["minwidth_metal1"]) def route_input_B(self): """create input B layout """ - xoffset = self.pmos2.poly_positions[0].x \ - + self.pmos_position2.x - yoffset = self.A_position.y \ - + 0.5 * (self.poly_contact.second_layer_width \ - + drc["minwidth_metal1"]) + drc["metal1_to_metal1"] - self.B_position = vector(xoffset, yoffset) - offset = self.B_position - vector(0, 0.5 * self.poly_contact.width) + xoffset = self.pmos2.poly_positions[0].x + self.pmos_loc2.x + yoffset = self.A_loc.y + 0.5 * (self.poly_contact.second_layer_width \ + + drc["minwidth_metal1"]) + drc["metal1_to_metal1"] + self.B_loc = vector(xoffset, yoffset) + offset = self.B_loc - vector(0, 0.5 * self.poly_contact.width) self.add_contact(layers=("poly", "contact", "metal1"), offset=offset, rotate=90) @@ -381,49 +377,53 @@ class nor_2(design.design): width=-(self.poly_contact.first_layer_position.y + drc["minwidth_poly"]), height=self.poly_contact.first_layer_width) self.add_layout_pin(text="B", - layer="metal1", - offset=[0, - self.B_position.y - 0.5 * drc["minwidth_metal1"]], - width=self.B_position.x, - height=drc["minwidth_metal1"]) + layer="metal1", + offset=[0, self.B_loc.y - 0.5 * drc["minwidth_metal1"]], + width=self.B_loc.x, + height=drc["minwidth_metal1"]) def route_output(self): """route the output to nmos pmos """ - self.Z_position = vector(self.width, self.A_position.y) + self.Z_loc = vector(self.width, self.A_loc.y) # route nmos drain to Z - nmos_contact = (self.nmos_position1 - + self.nmos1.active_contact_positions[1] - + self.nmos1.active_contact.second_layer_position - + vector(self.nmos1.active_contact.second_layer_width, - 0).scale(0.5, 0)) - mid = [nmos_contact.x, self.A_position.y] - self.add_path("metal1", [self.Z_position, mid, nmos_contact]) - + nmos_contact = (self.nmos_loc1 + + self.nmos1.active_contact_positions[1] + + self.nmos1.active_contact.second_layer_position + + vector(self.nmos1.active_contact.second_layer_width,0).scale(0.5, 0)) + mid = [nmos_contact.x, self.A_loc.y] + self.add_path("metal1", [self.Z_loc, mid, nmos_contact]) + for i in range(len(self.pmos2.poly_positions) + 1): if i % 2 == 1: # pmos2 drain to Z - pmos_contact = (self.pmos_position2 - + self.pmos1.active_contact_positions[i] - + self.pmos2.active_contact.second_layer_position.scale(1, 0) - + vector(self.pmos2.active_contact.second_layer_width, - 0).scale(0.5, 0)) + pmos_contact = (self.pmos_loc2 + + self.pmos1.active_contact_positions[i] + + self.pmos2.active_contact.second_layer_position.scale(1, 0) + + vector(self.pmos2.active_contact.second_layer_width,0).scale(0.5, 0)) offset = pmos_contact - vector(0.5 * self.m1m2_via.width, 0) self.add_via(layers=("metal1", "via1", "metal2"), offset=offset) - mid = [pmos_contact.x, self.Z_position.y] + mid = [pmos_contact.x, self.Z_loc.y] self.add_wire(("metal1", "via1", "metal2"), - [self.Z_position, mid, pmos_contact]) + [self.Z_loc, mid, pmos_contact]) + + self.add_layout_pin(text="Z", + layer="metal1", + offset=mid - vector(0,0.5*drc["minwidth_metal1"]), + width=self.Z_loc.x-mid[0], + height=drc["minwidth_metal1"]) + def extend_wells(self): """ extend well for well contact""" - middle_point = (self.nmos_position1.y - + self.nmos1.pwell_position.y - + self.nmos1.well_height - + (self.pmos_position1.y - + self.pmos1.nwell_position.y - - self.nmos_position1.y - - self.nmos1.pwell_position.y - - self.nmos1.well_height) / 2 ) + middle_point = (self.nmos_loc1.y + + self.nmos1.pwell_position.y + + self.nmos1.well_height + + (self.pmos_loc1.y + + self.pmos1.nwell_position.y + - self.nmos_loc1.y + - self.nmos1.pwell_position.y + - self.nmos1.well_height) / 2 ) self.nwell_position = vector(0, middle_point) self.nwell_height = self.height - middle_point self.add_rect(layer="nwell", @@ -449,11 +449,11 @@ class nor_2(design.design): def extend_active(self): """ extend active for well contact""" self.active_width = self.pmos1.active_width \ - + drc["active_to_body_active"] \ - + self.pmos1.active_contact.width - offset = (self.pmos_position2.scale(1,0) - + self.pmos_position1.scale(0,1) - + self.pmos1.active_position) + + drc["active_to_body_active"] \ + + self.pmos1.active_contact.width + offset = (self.pmos_loc2.scale(1,0) + + self.pmos_loc1.scale(0,1) + + self.pmos1.active_position) self.add_rect(layer="active", offset=offset, width=self.active_width, @@ -466,9 +466,9 @@ class nor_2(design.design): height=self.pmos1.active_height) offset = (self.nmos1.active_position.scale(1,-1) - + self.nmos_position2.scale(1,0) - + self.nmos_position1.scale(0,1) - + vector(0, self.nmos1.height - self.nmos1.active_height)) + + self.nmos_loc2.scale(1,0) + + self.nmos_loc1.scale(0,1) + + vector(0, self.nmos1.height - self.nmos1.active_height)) self.add_rect(layer="active", offset=offset, width=self.active_width, diff --git a/compiler/path.py b/compiler/path.py index 31fd787d..0428a09e 100644 --- a/compiler/path.py +++ b/compiler/path.py @@ -2,6 +2,7 @@ from tech import drc from tech import layer as techlayer import debug from vector import vector +from utils import snap_to_grid class path(): """ @@ -34,7 +35,7 @@ class path(): def create_rectilinear_route(self): """ Add intermediate nodes if it isn't rectilinear. Also skip repeated nodes. Also, convert to vector if the aren't.""" - pl = self.position_list + pl = [snap_to_grid(x) for x in self.position_list] self.position_list = [] for index in range(len(pl) - 1): @@ -74,7 +75,6 @@ class path(): using the position list of the corners. """ pl = self.position_list # position list for index in range(len(pl) - 1): - # if we have x motion if pl[index][0] != pl[index + 1][0]: line_length = pl[index + 1][0] - pl[index][0] diff --git a/compiler/pin_layout.py b/compiler/pin_layout.py new file mode 100644 index 00000000..a709707b --- /dev/null +++ b/compiler/pin_layout.py @@ -0,0 +1,137 @@ +from vector import vector +from tech import layer + +class pin_layout: + """ + A class to represent a rectangular design pin. It is limited to a + single shape. + """ + + def __init__(self, name, rect, layer_name_num): + self.name = name + # repack the rect as a vector, just in case + if type(rect[0])==vector: + self.rect = rect + else: + self.rect = [vector(rect[0]),vector(rect[1])] + # if it's a layer number look up the layer name. this assumes a unique layer number. + if type(layer_name_num)==int: + self.layer = layer.keys()[layer.values().index(layer_name_num)] + else: + self.layer=layer_name_num + + def __str__(self): + """ override print function output """ + return "({} layer={} ll={} ur={})".format(self.name,self.layer,self.rect[0],self.rect[1]) + + def __repr__(self): + """ override print function output """ + return "({} layer={} ll={} ur={})".format(self.name,self.layer,self.rect[0],self.rect[1]) + + def height(self): + """ Return height. Abs is for pre-normalized value.""" + return abs(self.rect[1].y-self.rect[0].y) + + def width(self): + """ Return width. Abs is for pre-normalized value.""" + return abs(self.rect[1].x-self.rect[0].x) + + def normalize(self): + """ Re-find the LL and UR points after a transform """ + (first,second)=self.rect + ll = vector(min(first[0],second[0]),min(first[1],second[1])) + ur = vector(max(first[0],second[0]),max(first[1],second[1])) + self.rect=[ll,ur] + + def transform(self,offset,mirror,rotate): + """ Transform with offset, mirror and rotation to get the absolute pin location. + We must then re-find the ll and ur. The master is the cell instance. """ + (ll,ur) = self.rect + if mirror=="MX": + ll=ll.scale(1,-1) + ur=ur.scale(1,-1) + elif mirror=="MY": + ll=ll.scale(-1,1) + ur=ur.scale(-1,1) + elif mirror=="XY": + ll=ll.scale(-1,-1) + ur=ur.scale(-1,-1) + + if rotate==90: + ll=ll.rotate_scale(-1,1) + ur=ur.rotate_scale(-1,1) + elif rotate==180: + ll=ll.scale(-1,-1) + ur=ur.scale(-1,-1) + elif rotate==270: + ll=ll.rotate_scale(1,-1) + ur=ur.rotate_scale(1,-1) + + self.rect=[offset+ll,offset+ur] + self.normalize() + + def center(self): + return vector(0.5*(self.rect[0].x+self.rect[1].x),0.5*(self.rect[0].y+self.rect[1].y)) + + def cx(self): + """ Center x """ + return 0.5*(self.rect[0].x+self.rect[1].x) + + def cy(self): + """ Center y """ + return 0.5*(self.rect[0].y+self.rect[1].y) + + # The four possible corners + def ll(self): + """ Lower left point """ + return self.rect[0] + + def ul(self): + """ Upper left point """ + return vector(self.rect[0].x,self.rect[1].y) + + def br(self): + """ Bottom right point """ + return vector(self.rect[1].x,self.rect[0].y) + + def ur(self): + """ Upper right point """ + return self.rect[1] + + # The possible y edge values + def uy(self): + """ Upper y value """ + return self.rect[1].y + + def by(self): + """ Bottom y value """ + return self.rect[0].y + + # The possible x edge values + + def lx(self): + """ Left x value """ + return self.rect[0].x + + def rx(self): + """ Right x value """ + return self.rect[1].x + + + # The edge centers + def rc(self): + """ Right center point """ + return vector(self.rect[1].x,0.5*(self.rect[0].y+self.rect[1].y)) + + def lc(self): + """ Left center point """ + return vector(self.rect[0].x,0.5*(self.rect[0].y+self.rect[1].y)) + + def uc(self): + """ Upper center point """ + return vector(0.5*(self.rect[0].x+self.rect[1].x),self.rect[1].y) + + def bc(self): + """ Bottom center point """ + return vector(0.5*(self.rect[0].x+self.rect[1].x),self.rect[0].y) + diff --git a/compiler/pinv.py b/compiler/pinv.py index 2485549c..a80c5cda 100644 --- a/compiler/pinv.py +++ b/compiler/pinv.py @@ -18,7 +18,7 @@ class pinv(design.design): unique_id = 1 - def __init__(self, nmos_width=1, beta=3, height=bitcell.chars["height"], route_output=True): + def __init__(self, nmos_width=drc["minwidth_tx"], beta=parameter["pinv_beta"], height=bitcell.height, route_output=True): """Constructor : Creates a cell for a simple inverter""" name = "pinv{0}".format(pinv.unique_id) pinv.unique_id += 1 @@ -64,7 +64,6 @@ class pinv(design.design): self.connect_rails() self.connect_tx() self.route_pins() - self.setup_layout_offsets() def determine_tx_mults(self): """Determines the number of fingers needed to achieve same size with a height constraint""" @@ -75,7 +74,7 @@ class pinv(design.design): # this should be 2*poly extension beyond active? minwidth_box_poly = 2 * drc["minwidth_poly"] \ + drc["poly_to_poly"] - well_to_well = max(drc["pwell_enclose_nwell"], + well_to_well = max(drc["pwell_to_nwell"], minwidth_poly_contact, minwidth_box_poly) @@ -126,17 +125,17 @@ class pinv(design.design): self.gnd_position = vector(0, - 0.5 * drc["minwidth_metal1"]) # for tiling purposes self.add_layout_pin(text="gnd", - layer="metal1", - offset=self.gnd_position, - width=rail_width, - height=rail_height) + layer="metal1", + offset=self.gnd_position, + width=rail_width, + height=rail_height) self.vdd_position = vector(0, self.height - 0.5 * drc["minwidth_metal1"]) self.add_layout_pin(text="vdd", - layer="metal1", - offset=self.vdd_position, - width=rail_width, - height=rail_height) + layer="metal1", + offset=self.vdd_position, + width=rail_width, + height=rail_height) def add_ptx(self): """Adds pmos and nmos to the layout""" @@ -294,10 +293,9 @@ class pinv(design.design): width=self.poly_contact.first_layer_position.y + drc["minwidth_poly"], height=self.poly_contact.first_layer_width) - input_length = self.pmos.poly_positions[0].x \ - - self.poly_contact.height + input_length = self.pmos.poly_positions[0].x - self.poly_contact.height # Determine the y-coordinate for the placement of the metal1 via - self.input_position = vector(0, .5*(self.height - drc["minwidth_metal1"] + self.input_position = vector(0, 0.5*(self.height - drc["minwidth_metal1"] + self.nmos.height - self.pmos.height)) self.add_layout_pin(text="A", layer="metal1", @@ -313,16 +311,18 @@ class pinv(design.design): self.input_position.y) output_length = self.width - offset.x if self.route_output == True: - self.output_position = offset + vector(output_length,0) - self.add_rect(layer="metal1", - offset=offset, - width=output_length, - height=drc["minwidth_metal1"]) + # This extends the output to the edge of the cell + self.add_layout_pin(text="Z", + layer="metal1", + offset=offset, + width=output_length, + height=drc["minwidth_metal1"]) else: - self.output_position = offset - self.add_label(text="Z", - layer="metal1", - offset=offset) + # This leaves the output as an internal pin (min sized) + self.add_layout_pin(text="Z", + layer="metal1", + offset=offset) + def add_well_contacts(self): """Adds n/p well taps to the layout""" @@ -403,10 +403,6 @@ class pinv(design.design): self.route_input_gate() self.route_output_drain() - def setup_layout_offsets(self): - self.A_position = self.input_position - self.Z_position = self.output_position - def input_load(self): return ((self.nmos_size+self.pmos_size)/parameter["min_tx_size"])*spice["min_tx_gate_c"] diff --git a/compiler/precharge.py b/compiler/precharge.py index e64370b6..f84fa977 100644 --- a/compiler/precharge.py +++ b/compiler/precharge.py @@ -18,10 +18,11 @@ class precharge(design.design): c = reload(__import__(OPTS.config.bitcell)) self.mod_bitcell = getattr(c, OPTS.config.bitcell) - self.bitcell_chars = self.mod_bitcell.chars - + self.bitcell = self.mod_bitcell() + self.ptx_width = ptx_width self.beta = beta + self.width = self.bitcell.width self.add_pins() self.create_layout() @@ -33,7 +34,6 @@ class precharge(design.design): def create_layout(self): self.create_ptx() self.create_contacts() - self.setup_layout_constants() self.add_ptx() self.connect_poly() self.add_pclk() @@ -46,11 +46,9 @@ class precharge(design.design): def create_ptx(self): """Initializes the upper and lower pmos""" self.lower_pmos = ptx(width=self.ptx_width, - mults=1, tx_type="pmos") self.add_mod(self.lower_pmos) self.upper_pmos = ptx(width=self.beta * self.ptx_width, - mults=1, tx_type="pmos") self.upper_pmos = self.upper_pmos self.add_mod(self.upper_pmos) @@ -72,10 +70,6 @@ class precharge(design.design): self.lower_contact = contact(layer_stack=("metal1", "via1", "metal2"), dimensions=self.lower_dimensions) - def setup_layout_constants(self): - self.width = self.bitcell_chars["width"] - self.BL_position = vector(self.bitcell_chars["BL"][0], 0) - self.BR_position = vector(self.bitcell_chars["BR"][0], 0) def add_ptx(self): """Adds both the upper_pmos and lower_pmos to the module""" @@ -144,10 +138,10 @@ class precharge(design.design): offset.y= offset.y + self.poly_contact.second_layer_position.x self.pclk_position = vector(0, offset.y) self.add_layout_pin(text="clk", - layer="metal1", - offset=self.pclk_position, - width=self.width, - height=drc["minwidth_metal1"]) + layer="metal1", + offset=self.pclk_position, + width=self.width, + height=drc["minwidth_metal1"]) def add_nwell_contact(self): """Adds a nwell tap to connect to the vdd rail""" @@ -182,11 +176,11 @@ class precharge(design.design): def add_vdd_rail(self): """Adds a vdd rail at the top of the cell""" # adds the rail across the width of the cell - self.vdd_position = vector(self.pclk_position.x, - self.height - drc["minwidth_metal1"]) + vdd_position = vector(self.pclk_position.x, + self.height - drc["minwidth_metal1"]) self.add_layout_pin(text="vdd", layer="metal1", - offset=self.vdd_position, + offset=vdd_position, width=self.width, height=drc["minwidth_metal1"]) @@ -206,16 +200,16 @@ class precharge(design.design): def add_bitlines(self): """Adds both bit-line and bit-line-bar to the module""" # adds the BL on metal 2 - offset = self.BL_position - vector(0.5 * drc["minwidth_metal2"],0) - self.add_layout_pin(text="bl", + offset = vector(self.bitcell.get_pin("BL").cx(),0) - vector(0.5 * drc["minwidth_metal2"],0) + self.add_layout_pin(text="BL", layer="metal2", offset=offset, width=drc['minwidth_metal2'], height=self.height) # adds the BR on metal 2 - offset = self.BR_position - vector(0.5 * drc["minwidth_metal2"],0) - self.add_layout_pin(text="br", + offset = vector(self.bitcell.get_pin("BR").cx(),0) - vector(0.5 * drc["minwidth_metal2"],0) + self.add_layout_pin(text="BR", layer="metal2", offset=offset, width=drc['minwidth_metal2'], @@ -277,8 +271,8 @@ class precharge(design.design): """Connects bit-lines to lower_pmos""" mos,mos_pos,contact = dest mos_active = (mos_pos + mos.active_contact_positions[0]) - offset = vector(self.BL_position.x, mos_active.y) - xlength = (mos_active.x + correct_x - self.BL_position.x + offset = vector(self.bitcell.get_pin("BL").cx() , mos_active.y) + xlength = (mos_active.x + correct_x - offset.x + 0.5 * drc["minwidth_metal2"]) self.add_rect(layer="metal2", offset=offset, @@ -290,7 +284,7 @@ class precharge(design.design): mos,mos_pos,contact = dest offset = mos_pos + vector(correct_x, mos.active_contact_positions[0].y) - xlength = self.BR_position.x - offset.x - 0.5 * drc["minwidth_metal2"] + xlength = self.bitcell.get_pin("BR").cx() - offset.x - 0.5 * drc["minwidth_metal2"] self.add_rect(layer="metal2", offset=offset, width=xlength, diff --git a/compiler/precharge_array.py b/compiler/precharge_array.py index 1578eca8..a075f495 100644 --- a/compiler/precharge_array.py +++ b/compiler/precharge_array.py @@ -11,14 +11,22 @@ class precharge_array(design.design): of bit line columns, height is the height of the bit-cell array. """ - def __init__(self, name, columns, ptx_width, beta=2): - design.design.__init__(self, name) - debug.info(1, "Creating {0}".format(name)) + def __init__(self, columns, ptx_width, beta=2): + design.design.__init__(self, "precharge_array") + debug.info(1, "Creating {0}".format(self.name)) self.columns = columns self.ptx_width = ptx_width self.beta = beta + self.pc_cell = precharge(name="precharge_cell", + ptx_width=self.ptx_width, + beta=self.beta) + self.add_mod(self.pc_cell) + + self.width = self.columns * self.pc_cell.width + self.height = self.pc_cell.height + self.add_pins() self.create_layout() self.DRC_LVS() @@ -32,49 +40,23 @@ class precharge_array(design.design): self.add_pin("vdd") def create_layout(self): - self.create_pc_cell() - self.setup_layout_constants() - self.add_pc() - self.add_rails() - self.offset_all_coordinates() + self.add_insts() - def setup_layout_constants(self): - self.vdd_positions = [] - self.BL_positions = [] - self.BR_positions = [] - - self.width = self.columns * self.pc_cell.width - self.height = self.pc_cell.height - - def add_rails(self): - self.add_vdd_rail() - self.add_pclk_rail() - - def add_vdd_rail(self): - offset = self.pc_cell.vdd_position self.add_layout_pin(text="vdd", layer="metal1", - offset=offset, + offset=self.pc_cell.get_pin("vdd").ll(), width=self.width, height=drc["minwidth_metal1"]) - self.vdd_positions.append(offset) - - def add_pclk_rail(self): - self.pclk_position = self.pc_cell.pclk_position + self.add_layout_pin(text="clk", layer="metal1", - offset=self.pclk_position, + offset=self.pc_cell.get_pin("clk").ll(), width=self.width, height=drc["minwidth_metal1"]) + + #self.offset_all_coordinates() - def create_pc_cell(self): - """Initializes a single precharge cell""" - self.pc_cell = precharge(name="precharge_cell", - ptx_width=self.ptx_width, - beta=self.beta) - self.add_mod(self.pc_cell) - - def add_pc(self): + def add_insts(self): """Creates a precharge array by horizontally tiling the precharge cell""" self.pc_cell_positions = [] for i in range(self.columns): @@ -84,14 +66,12 @@ class precharge_array(design.design): self.add_inst(name=name, mod=self.pc_cell, offset=offset) - self.add_label(text="bl[{0}]".format(i), - layer="metal2", - offset=offset+ self.pc_cell.BL_position.scale(1,0)) - self.add_label(text="br[{0}]".format(i), - layer="metal2", - offset=offset+ self.pc_cell.BR_position.scale(1,0)) + self.add_layout_pin(text="bl[{0}]".format(i), + layer="metal2", + offset=offset+ self.pc_cell.get_pin("BL").ll().scale(1,0)) + self.add_layout_pin(text="br[{0}]".format(i), + layer="metal2", + offset=offset+ self.pc_cell.get_pin("BR").ll().scale(1,0)) self.connect_inst(["bl[{0}]".format(i), "br[{0}]".format(i), "clk", "vdd"]) - self.BL_positions.append(offset + self.pc_cell.BL_position.scale(1,0)) - self.BR_positions.append(offset + self.pc_cell.BR_position.scale(1,0)) diff --git a/compiler/ptx.py b/compiler/ptx.py index 6cf294ae..9a72f449 100644 --- a/compiler/ptx.py +++ b/compiler/ptx.py @@ -8,9 +8,10 @@ import re class ptx(design.design): """ This module generates gds and spice of a parametrically NMOS or PMOS sized transistor. - Creates a simple MOS transistor + Creates a simple MOS transistor. poly_positions are the ll of the poly gate. active_contact_positions + is an array of the positions of the ll of active contacts (left to right) """ - def __init__(self, width=1, mults=1, tx_type="nmos"): + def __init__(self, width=drc["minwidth_tx"], mults=1, tx_type="nmos"): name = "{0}_m{1}_w{2}".format(tx_type, mults, width) # remove periods for newer spice compatibility name=re.sub('\.','_',name) @@ -243,8 +244,7 @@ class ptx(design.design): # left_most contact column contact_xoffset = 0 - contact_yoffset = (self.active_height \ - - self.active_contact.height) / 2 + contact_yoffset = (self.active_height - self.active_contact.height) / 2 offset = vector(contact_xoffset, contact_yoffset) self.add_contact(layers=("active", "contact", "metal1"), offset=offset, diff --git a/compiler/replica_bitcell.py b/compiler/replica_bitcell.py index dd02a167..5ec524f4 100644 --- a/compiler/replica_bitcell.py +++ b/compiler/replica_bitcell.py @@ -10,13 +10,14 @@ class replica_bitcell(design.design): is a hand-made cell, so the layout and netlist should be available in the technology library. """ - pins = ["BL", "BR", "WL", "vdd", "gnd"] - chars = utils.auto_measure_libcell(pins, "replica_cell_6t", GDS["unit"], layer["boundary"]) + pin_names = ["BL", "BR", "WL", "vdd", "gnd"] + (width,height) = utils.get_libcell_size("replica_cell_6t", GDS["unit"], layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "replica_cell_6t", GDS["unit"], layer["boundary"]) - def __init__(self, name="replica_cell_6t"): - design.design.__init__(self, name) - debug.info(2, "Create bitcell object") + def __init__(self): + design.design.__init__(self, "replica_cell_6t") + debug.info(2, "Create replica bitcell object") - - self.width = replica_bitcell.chars["width"] - self.height = replica_bitcell.chars["height"] + self.width = replica_bitcell.width + self.height = replica_bitcell.height + self.pin_map = replica_bitcell.pin_map diff --git a/compiler/replica_bitline.py b/compiler/replica_bitline.py index a08a0def..75432255 100644 --- a/compiler/replica_bitline.py +++ b/compiler/replica_bitline.py @@ -16,8 +16,8 @@ class replica_bitline(design.design): Used for memory timing control """ - def __init__(self, name, rows): - design.design.__init__(self, "replica_bitline") + def __init__(self, rows, name="replica_bitline"): + design.design.__init__(self, name) g = reload(__import__(OPTS.config.delay_chain)) self.mod_delay_chain = getattr(g, OPTS.config.delay_chain) @@ -27,386 +27,303 @@ class replica_bitline(design.design): c = reload(__import__(OPTS.config.bitcell)) self.mod_bitcell = getattr(c, OPTS.config.bitcell) - self.bitcell_chars = self.mod_bitcell.chars for pin in ["en", "out", "vdd", "gnd"]: self.add_pin(pin) self.rows = rows self.create_modules() - self.cal_modules_offset() + self.calculate_module_offsets() self.add_modules() self.route() - self.offset_all_coordinates() + self.add_layout_pins() self.DRC_LVS() - def cal_modules_offset(self): - pinv_error_offset = 0.025 - # leave some room for metal1 routing - margin = 3 * drc["minwidth_metal1"] - # witdth + min_spacing of M1 & M2 - m1rail_space = drc["minwidth_metal1"] + drc["metal1_to_metal1"] - m2rail_space = drc["minwidth_metal2"] + drc["metal2_to_metal2"] - # leave some margin as bit cell layout exceeds its own orgin - route_margin = 8 * m2rail_space - well_margin = 2 * drc["pwell_enclose_nwell"] - bitcell_array_spacing = max(route_margin, well_margin) - # now extra space for BL and WL of RBC - gnd_route_margin = 5 * m2rail_space - - y_off = (self.inv.height * 2 + pinv_error_offset - + max(drc["pwell_enclose_nwell"], - m1rail_space * 4)) - self.delay_chain_offset = vector(self.delay_chain.height,y_off) - self.en_input_offset = vector(0, y_off - m2rail_space) - self.en_nor_offset = vector(self.nor.width + margin, - self.inv.height * 2) - self.BL_inv_offset = vector(self.en_nor_offset.x - self.inv.width, 0) - self.access_tx_offset = vector(self.en_nor_offset.x - self.nor.width - + self.access_tx.height + margin, - self.inv.height * 0.5) - self.replica_bitline_offset = vector(self.delay_chain_offset.x - + bitcell_array_spacing, - self.bitcell_chars["height"] - + gnd_route_margin) - self.delay_inv_offset = vector(self.delay_chain_offset.x - self.inv.width, - self.inv.height * 2) - - self.height = m1rail_space + max(self.delay_chain_offset.y + self.inv.height, - self.replica_bitline_offset.y - + self.bitline_load.height - + 0.5 * self.bitcell_chars["height"]) - self.width = (self.replica_bitline_offset.x + self.replica_bitcell.width) - - - def create_modules(self): - """ create module """ - self.replica_bitcell = self.mod_replica_bitcell() - self.add_mod(self.replica_bitcell) - - # This is the replica bitline load column that is the same height as our array - self.bitline_load = bitcell_array(name="bitline_load", - cols=1, - rows=self.rows) - self.add_mod(self.bitline_load) - - # FIXME: This just creates 3 1x inverters - self.delay_chain = self.mod_delay_chain("delay_chain", - [1, 1, 1]) - self.add_mod(self.delay_chain) - - self.inv = pinv(nmos_width=drc["minwidth_tx"]) - self.add_mod(self.inv) - + def calculate_module_offsets(self): + """ Calculate all the module offsets """ + # These aren't for instantiating, but we use them to get the dimensions + self.active_contact = contact(layer_stack=("active", "contact", "poly")) self.poly_contact = contact(layer_stack=("poly", "contact", "metal1")) + self.poly_contact_offset = vector(0.5*self.poly_contact.width,0.5*self.poly_contact.height) self.m1m2_via = contact(layer_stack=("metal1", "via1", "metal2")) self.m2m3_via = contact(layer_stack=("metal2", "via2", "metal3")) - self.nor = nor_2(nmos_width=drc["minwidth_tx"]) - self.add_mod(self.nor) + # M1/M2 routing pitch is based on contacted pitch + self.m1_pitch = max(self.m1m2_via.width,self.m1m2_via.height) + max(drc["metal1_to_metal1"],drc["metal2_to_metal2"]) + self.m2_pitch = max(self.m2m3_via.width,self.m2m3_via.height) + max(drc["metal2_to_metal2"],drc["metal3_to_metal3"]) + + # This corrects the offset pitch difference between M2 and M1 + self.offset_fix = vector(0.5*(drc["minwidth_metal2"]-drc["minwidth_metal1"]),0) - self.access_tx = ptx(width=drc["minwidth_tx"], - mults=1, - tx_type="pmos") + # delay chain will be rotated 90, so move it over a width + # we move it up a inv height just for some routing room + self.rbl_inv_offset = vector(self.delay_chain.height, self.inv.width) + # access TX goes right on top of inverter, leave space for an inverter which is + # about the same as a TX. We'll need to add rails though. + self.access_tx_offset = vector(1.5*self.inv.height,self.rbl_inv_offset.y) + vector(0,2.25*self.inv.width) + self.delay_chain_offset = self.rbl_inv_offset + vector(0,4*self.inv.width) + + # Replica bitline and such are not rotated, but they must be placed far enough + # away from the delay chain/inverter with space for three M2 tracks + self.bitcell_offset = self.rbl_inv_offset + vector(2*self.m2_pitch, 0) + vector(0, self.bitcell.height + self.inv.width) + + self.rbl_offset = self.bitcell_offset + + + self.height = self.rbl_offset.y + self.rbl.height + self.width = self.rbl_offset.x + self.bitcell.width + + + def create_modules(self): + """ Create modules for later instantiation """ + self.bitcell = self.replica_bitcell = self.mod_replica_bitcell() + self.add_mod(self.bitcell) + + # This is the replica bitline load column that is the height of our array + self.rbl = bitcell_array(name="bitline_load", cols=1, rows=self.rows) + self.add_mod(self.rbl) + + self.delay_chain = self.mod_delay_chain([1, 1, 1]) + self.add_mod(self.delay_chain) + + self.inv = pinv() + self.add_mod(self.inv) + + self.access_tx = ptx(tx_type="pmos") self.add_mod(self.access_tx) def add_modules(self): - """add mod instance in layout """ - self.add_inst(name="BL_inv", - mod=self.inv, - offset=self.BL_inv_offset) + """ Add all of the module instances in the logical netlist """ + # This is the threshold detect inverter on the output of the RBL + self.rbl_inv_inst=self.add_inst(name="rbl_inv", + mod=self.inv, + offset=self.rbl_inv_offset+vector(0,self.inv.width), + rotate=270, + mirror="MX") self.connect_inst(["bl[0]", "out", "vdd", "gnd"]) - self.add_inst(name="BL_access_tx", - mod=self.access_tx, - offset=self.access_tx_offset, - rotate=90) + self.tx_inst=self.add_inst(name="rbl_access_tx", + mod=self.access_tx, + offset=self.access_tx_offset, + rotate=90) # D, G, S, B self.connect_inst(["vdd", "delayed_en", "bl[0]", "vdd"]) + # add the well and poly contact - self.add_inst(name="delay_chain", - mod=self.delay_chain, - offset=self.delay_chain_offset, - rotate=90) + self.dc_inst=self.add_inst(name="delay_chain", + mod=self.delay_chain, + offset=self.delay_chain_offset, + rotate=90) self.connect_inst(["en", "delayed_en", "vdd", "gnd"]) - self.add_inst(name="bitcell", - mod=self.replica_bitcell, - offset=self.replica_bitline_offset, - mirror="MX") + self.rbc_inst=self.add_inst(name="bitcell", + mod=self.replica_bitcell, + offset=self.bitcell_offset, + mirror="MX") self.connect_inst(["bl[0]", "br[0]", "delayed_en", "vdd", "gnd"]) - self.add_loads() - self.expan_the_well_to_BL_inv() + self.rbl_inst=self.add_inst(name="load", + mod=self.rbl, + offset=self.rbl_offset) + self.connect_inst(["bl[0]", "br[0]"] + ["gnd"]*self.rows + ["vdd", "gnd"]) + - def expan_the_well_to_BL_inv(self): - width = self.BL_inv_offset.x - self.access_tx_offset.x + self.inv.width - well_offset = self.access_tx_offset - vector(self.access_tx.width, 0) - for layer in ["nwell", "vtg"]: - self.add_rect(layer=layer, - offset=well_offset, - width=width, - height= 2*self.access_tx.width) - def add_loads(self): - self.add_inst(name="load", - mod=self.bitline_load, - offset=self.replica_bitline_offset) - temp = [] - for i in range(1): - temp.append("bl[{0}]".format(i)) - temp.append("br[{0}]".format(i)) - for j in range(self.rows): - temp.append("gnd".format(j)) - temp = temp + ["vdd", "gnd"] - self.connect_inst(temp) def route(self): - """connect modules together""" - # calculate pin offset - correct = vector(0, 0.5 * drc["minwidth_metal1"]) - self.out_offset = self.BL_inv_offset + self.inv.Z_position + correct - self.add_via(layers=("metal1", "via1", "metal2"), - offset=self.out_offset) - m1_pin_offset = self.out_offset - correct - self.add_rect(layer="metal1", - offset=m1_pin_offset, - width=self.m1m2_via.width, - height=self.m1m2_via.height) - self.add_rect(layer="metal2", - offset=m1_pin_offset, - width=self.m2m3_via.width, - height=self.m2m3_via.height) - - BL_inv_in = self.BL_inv_offset + self.inv.A_position + correct - BL_offset = self.replica_bitline_offset + vector(1,0).scale(self.bitcell_chars["BL"]) - pin_offset = self.delay_chain.clk_out_offset.rotate_scale(-1,1) - delay_chain_output = self.delay_chain_offset + pin_offset - vdd_offset = vector(self.delay_chain_offset.x + 9 * drc["minwidth_metal2"], - self.height) - self.create_input() - - self.route_BL_t_BL_inv(BL_offset, BL_inv_in) - self.route_access_tx(delay_chain_output, BL_inv_in, vdd_offset) - self.route_vdd() + """ Connect all the signals together """ self.route_gnd() - # route loads after gnd and vdd created - self.route_loads(vdd_offset) - self.route_RC(vdd_offset) + self.route_vdd() + self.route_access_tx() - def create_input(self): - # create routing module based on module offset - correct = vector(0.5 * drc["minwidth_metal1"], 0) - pin_offset = self.delay_chain.clk_in_offset.rotate_scale(-1,1) - input_offset = self.delay_chain_offset + pin_offset + correct - mid1 = [input_offset.x, self.en_input_offset.y] - self.add_path("metal1", [self.en_input_offset, mid1, input_offset]) - self.add_label(text="en", - layer="metal1", - offset=self.en_input_offset) + def route_access_tx(self): + # GATE ROUTE + # 1. Add the poly contact and nwell enclosure + # Determines the y-coordinate of where to place the gate input poly pin + # (middle in between the pmos and nmos) - def route_BL_t_BL_inv(self, BL_offset, BL_inv_in): - # BL_inv input to M3 - mid1 = BL_inv_in - vector(0, - drc["metal2_to_metal2"] + self.m1m2_via.width) - mid2 = vector(self.en_nor_offset.x + 3 * drc["metal1_to_metal1"], - mid1.y) - mid3 = vector(mid2.x, - self.replica_bitline_offset.y - self.replica_bitcell.height - - 0.5 * (self.m1m2_via.height + drc["metal1_to_metal1"]) - - 2 * drc["metal1_to_metal1"]) - self.add_wire(layers=("metal2", "via1", "metal1"), - coordinates=[BL_inv_in, mid1, mid2, mid3]) - - # need to fix the mid point as this is done with two wire - # this seems to cover the metal1 error of the wire - offset = mid3 - vector( [0.5 * drc["minwidth_metal1"]] * 2) - self.add_rect(layer="metal1", - offset=offset, - width=drc["minwidth_metal1"], - height=drc["minwidth_metal1"]) - - mid4 = [BL_offset.x, mid3.y] - self.add_wire(layers=("metal1", "via1", "metal2"), - coordinates=[BL_offset, mid4, mid3]) - - def route_access_tx(self, delay_chain_output, BL_inv_in, vdd_offset): - self.route_tx_gate(delay_chain_output) - self.route_tx_drain(vdd_offset) - self.route_tx_source(BL_inv_in) - - def route_tx_gate(self, delay_chain_output): - # gate input for access tx - offset = (self.access_tx.poly_positions[0].rotate_scale(0,1) - + self.access_tx_offset) - width = -6 * drc["minwidth_metal1"] - self.add_rect(layer="poly", - offset=offset, - width=width, - height=drc["minwidth_poly"]) - y_off = 0.5 * (drc["minwidth_poly"] - self.poly_contact.height) - offset = offset + vector(width, y_off) + # finds the lower right of the poly gate + poly_offset = self.access_tx_offset + self.access_tx.poly_positions[0].rotate_scale(-1,1) + # This centers the contact on the poly + contact_offset = poly_offset.scale(0,1) + self.dc_inst.get_pin("out").ll().scale(1,0) \ + + vector(-drc["poly_extend_contact"], -0.5*self.poly_contact.height + 0.5*drc["minwidth_poly"]) self.add_contact(layers=("poly", "contact", "metal1"), - offset=offset) - # route gate to delay_chain output - gate_offset = offset + vector(0.5 * drc["minwidth_metal1"], - 0.5 * self.poly_contact.width) - self.route_access_tx_t_delay_chain(gate_offset, delay_chain_output) - self.route_access_tx_t_WL(gate_offset) + offset=contact_offset) + self.add_rect(layer="poly", + offset=poly_offset, + width=contact_offset.x-poly_offset.x, + height=drc["minwidth_poly"]) + nwell_offset = self.rbl_inv_offset + vector(-self.inv.height,self.inv.width) + self.add_rect(layer="nwell", + offset=nwell_offset, + width=0.5*self.inv.height, + height=self.delay_chain_offset.y-nwell_offset.y) - def route_access_tx_t_delay_chain(self, offset, delay_chain_output): - m2rail_space = (drc["minwidth_metal2"] + drc["metal2_to_metal2"]) - mid1 = vector(offset.x, self.delay_chain_offset.y - 3 * m2rail_space) - mid2 = [delay_chain_output.x, mid1.y] - # Note the inverted wire stack - self.add_wire(layers=("metal1", "via1", "metal2"), - coordinates=[offset, mid1, mid2, delay_chain_output]) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=delay_chain_output, - mirror="MX") + # 2. Route delay chain output to access tx gate + delay_en_offset = self.dc_inst.get_pin("out").bc() + delay_en_end_offset = contact_offset + vector(self.poly_contact.width,self.poly_contact.height).scale(0.5,0.5) + self.add_path("metal1", [delay_en_offset,delay_en_end_offset]) - def route_access_tx_t_WL(self, offset): - m1m2_via_offset = offset - vector(0.5 * self.m1m2_via.width, - 0.5 * self.m1m2_via.height) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=m1m2_via_offset) - # route gate to RC WL - RC_WL = self.replica_bitline_offset - vector(0,1).scale(self.bitcell_chars["WL"]) - mid1 = vector(offset.x, 0) - mid2 = vector(self.en_nor_offset.x + 3 * drc["metal1_to_metal1"], - mid1.y) - mid3 = vector(RC_WL.x - drc["minwidth_metal1"] - self.m1m2_via.height, - mid1.y) - mid4 = vector(mid3.x, RC_WL.y) - self.add_path("metal2", [offset, mid1, mid2, mid3, mid4]) - offset = mid4 - vector([0.5 * drc["minwidth_metal1"]] * 2) - width = RC_WL.x - offset.x - # enter the bit line array with metal1 - via_offset = [mid4.x - 0.5 * self.m1m2_via.width, - offset.y - - 0.5 * (self.m1m2_via.height - - drc["minwidth_metal1"])] - self.add_via(layers=("metal1", "via1", "metal2"), - offset=via_offset) - self.add_rect(layer="metal1", - offset=offset, - width=width, - height=drc["minwidth_metal1"]) + # 3. Route the mid-point of previous route to the bitcell WL + # route bend of previous net to bitcell WL + wl_offset = self.rbc_inst.get_pin("WL").lc() + wl_mid = vector(delay_en_end_offset.x,wl_offset.y) + self.add_path("metal1", [delay_en_end_offset, wl_mid, wl_offset]) - def route_tx_drain(self,vdd_offset): - # route drain to Vdd - active_offset = self.access_tx.active_contact_positions[1].rotate_scale(-1,1) - correct = vector(-0.5 * drc["minwidth_metal1"], - 0.5 * self.access_tx.active_contact.width) - drain_offset = self.access_tx_offset + active_offset + correct - close_Vdd_offset = self.BL_inv_offset + vector(0, self.inv.height) - self.add_path("metal1", [drain_offset, close_Vdd_offset]) - - mid = [vdd_offset.x, close_Vdd_offset.y] - self.add_wire(layers=("metal1", "via1", "metal2"), - coordinates=[close_Vdd_offset, mid, vdd_offset]) - - def route_tx_source(self, BL_inv_in): - # route source to BL inv input which is connected to BL - active_offset = self.access_tx.active_contact_positions[0].rotate_scale(-1,1) - correct = vector(-0.5 * drc["minwidth_metal1"], - 0.5 * self.access_tx.active_contact.width) - source_offset = self.access_tx_offset + active_offset + correct - self.add_path("metal1", [source_offset, BL_inv_in]) + # SOURCE ROUTE + # Route the source to the vdd rail + source_offset = self.access_tx_offset + self.access_tx.active_contact_positions[1].rotate_scale(-1,1)\ + + vector(self.active_contact.width,self.active_contact.height).rotate_scale(-0.5,0.5) + inv_vdd_offset = self.rbl_inv_inst.get_pin("vdd").uc() + vdd_offset = inv_vdd_offset.scale(1,0) + source_offset.scale(0,1) + self.add_path("metal1", [source_offset, vdd_offset]) + + # DRAIN ROUTE + # Route the drain to the RBL inverter input + drain_offset = self.access_tx_offset + self.access_tx.active_contact_positions[0].rotate_scale(-1,1) \ + + self.poly_contact_offset.rotate_scale(-1,1) + mid1 = drain_offset.scale(1,0) + vector(0,self.rbl_inv_offset.y+self.inv.width+self.m2_pitch) + inv_A_offset = self.rbl_inv_inst.get_pin("A").uc() + mid2 = vector(inv_A_offset.x, mid1.y) + self.add_path("metal1",[drain_offset, mid1, mid2, inv_A_offset]) + + # Route the connection of the drain route (mid2) to the RBL bitline (left) + drain_offset = mid2 + # Route the M2 to the right of the vdd rail between rbl_inv and bitcell + gnd_pin = self.rbl_inv_inst.get_pin("gnd").ll() + mid1 = vector(gnd_pin.x+self.m2_pitch,drain_offset.y) + # Via will go halfway down from the bitcell + bl_offset = self.rbc_inst.get_pin("BL").bc() + via_offset = bl_offset - vector(0,0.5*self.inv.width) + mid2 = vector(mid1.x,via_offset.y) + # self.add_contact(layers=("metal1", "via1", "metal2"), + # offset=via_offset - vector(0.5*drc["minwidth_metal2"],0.5*drc["minwidth_metal1"])) + self.add_wire(("metal1","via1","metal2"),[drain_offset,mid1,mid2,via_offset,bl_offset]) + #self.add_path("metal2",[via_offset,bl_offset]) + def route_vdd(self): - vdd_offset = vector(0, self.height) + # Add a rail in M2 that is to the right of the inverter gnd pin + # The replica column may not fit in a single standard cell pitch, so add the vdd rail to the + # right of it. + vdd_start = vector(self.bitcell_offset.x + self.bitcell.width + self.m1_pitch,0) + # It is the height of the entire RBL and bitcell self.add_layout_pin(text="vdd", - layer="metal1", - offset=vdd_offset, - width=self.width, + layer="metal1", + offset=vdd_start, + width=drc["minwidth_metal1"], + height=self.rbl.height+self.bitcell.height+2*self.inv.width+0.5*drc["minwidth_metal1"]) + + # Connect the vdd pins of the bitcell load directly to vdd + vdd_pins = self.rbl_inst.get_pin("vdd") + for pin in vdd_pins: + offset = vector(vdd_start.x,pin.by()) + self.add_rect(layer="metal1", + offset=offset, + width=self.rbl_offset.x-vdd_start.x, + height=drc["minwidth_metal1"]) + + # Also connect the replica bitcell vdd pin to vdd + pin = self.rbc_inst.get_pin("vdd") + offset = vector(vdd_start.x,pin.by()) + self.add_rect(layer="metal1", + offset=offset, + width=self.bitcell_offset.x-vdd_start.x, height=drc["minwidth_metal1"]) - # delay chain vdd to vertical vdd rail and - start = self.delay_chain_offset - vector(0.5 * self.delay_chain.height, 0) - m1rail_space = (drc["minwidth_metal1"] + drc["metal1_to_metal1"]) - mid1 = start - vector(0, m1rail_space) - mid2 = vector(self.delay_chain_offset.x + 9 * drc["minwidth_metal2"], - mid1.y) - end = [mid2.x, vdd_offset.y] - self.add_path(layer=("metal1"), - coordinates=[start, mid1, mid2]) - self.add_wire(layers=("metal1", "via1", "metal2"), - coordinates=[mid1, mid2, end]) + + # Add a second vdd pin. No need for full length. It is must connect at the next level. + inv_vdd_offset = self.rbl_inv_inst.get_pin("vdd").ll() + self.add_layout_pin(text="vdd", + layer="metal1", + offset=inv_vdd_offset.scale(1,0), + width=drc["minwidth_metal1"], + height=self.delay_chain_offset.y) + + + def route_gnd(self): - """route gnd of delay chain, en_nor, en_inv and BL_inv""" - # route delay chain gnd to BL_inv gnd - # gnd Node between BL_inv access tx and delay chain, and is below - # en_input - self.gnd_position = self.delay_chain_offset - BL_gnd_offset = self.BL_inv_offset - mid1 = vector(0, self.BL_inv_offset.y) - rail2_space = drc["minwidth_metal2"] + drc["metal2_to_metal2"] - y_off = self.gnd_position.y + self.delay_chain.width + rail2_space - mid2 = vector(mid1.x, y_off) - share_gnd = vector(self.gnd_position.x, mid2.y) - # Note the inverted stacks - lst = [BL_gnd_offset, mid1, mid2, share_gnd, self.gnd_position] - self.add_wire(layers=("metal1", "via1", "metal2"), - coordinates=lst) - self.add_label(text="gnd", - layer="metal1", - offset=self.gnd_position) - # connect to the metal1 gnd of delay chain - offset = mid2 - vector(0.5 * drc["minwidth_metal1"], 0) - self.add_rect(layer="metal1", - offset=offset, - width=drc["minwidth_metal1"], - height=-self.delay_chain.width) - offset = [offset.x + self.delay_chain.height, - mid2.y] - self.add_rect(layer="metal1", - offset=offset, - width=drc["minwidth_metal1"], - height=-self.delay_chain.width) + """ Route all signals connected to gnd """ + + # Add a rail in M1 from bottom to two along delay chain + gnd_start = self.rbl_inv_inst.get_pin("gnd").ll() - self.offset_fix + + # It is the height of the entire RBL and bitcell + self.add_rect(layer="metal2", + offset=gnd_start, + width=drc["minwidth_metal2"], + height=self.rbl.height+self.bitcell.height+self.inv.width) + self.add_layout_pin(text="gnd", + layer="metal1", + offset=gnd_start.scale(1,0), + width=drc["minwidth_metal2"], + height=2*self.inv.width) + + # Connect the WL pins directly to gnd + for row in range(self.rows): + wl = "wl[{}]".format(row) + pin = self.rbl_inst.get_pin(wl) + offset = vector(gnd_start.x,pin.by()) + self.add_rect(layer="metal1", + offset=offset, + width=self.rbl_offset.x-gnd_start.x, + height=drc["minwidth_metal1"]) + self.add_via(layers=("metal1", "via1", "metal2"), + offset=offset) - def route_loads(self,vdd_offset): - """connect all the loads word line to gnd""" + # Add via for the delay chain + offset = self.delay_chain_offset - vector(0.5*drc["minwidth_metal1"],0) - self.offset_fix self.add_via(layers=("metal1", "via1", "metal2"), - offset=vdd_offset, - mirror="MX") - gnd_offset = (self.delay_chain_offset - + vector([drc["minwidth_metal1"]] * 2).scale(-.5,.5)) - for i in range(self.rows): - WL_offset = (self.replica_bitline_offset - + self.bitline_load.WL_positions[i].scale(0,1)) - mid = [self.delay_chain_offset.x + 6 * drc["minwidth_metal2"], - gnd_offset.y] - self.add_wire(layers=("metal1", "via1", "metal2"), - coordinates=[gnd_offset, mid, WL_offset]) - if i % 2 == 0: - load_vdd_offset = (self.replica_bitline_offset - + self.bitline_load.vdd_positions[i]) - mid = [vdd_offset.x, load_vdd_offset.y] - self.add_wire(layers=("metal1", "via1", "metal2"), - coordinates=[vdd_offset, mid, load_vdd_offset]) + offset=offset) - def route_RC(self,vdd_offset): - """route vdd gnd to the replica cell """ - # connect vdd - RC_vdd = self.replica_bitline_offset + vector(1,-1).scale(self.bitcell_chars["vdd"]) - mid = [vdd_offset.x, RC_vdd.y] - # Note the inverted stacks - self.add_wire(layers=("metal1", "via1", "metal2"), - coordinates=[vdd_offset, mid, RC_vdd]) + # Add via for the inverter + offset = self.rbl_inv_offset - vector(0.5*drc["minwidth_metal1"],self.m1m2_via.height) - self.offset_fix + self.add_via(layers=("metal1", "via1", "metal2"), + offset=offset) - gnd_offset = self.BL_inv_offset - vector(self.inv.width, 0) - load_gnd = self.replica_bitline_offset + vector(self.bitcell_chars["gnd"][0], - self.bitline_load.height) - mid = [load_gnd.x, gnd_offset.y] - self.add_wire(layers=("metal1", "via1", "metal2"), - coordinates=[gnd_offset, mid, load_gnd]) + # Connect the bitcell gnd pin to the rail + gnd_pins = self.get_pin("gnd") + gnd_start = self.get_pin("gnd").uc() + rbl_gnd_pins = self.rbl_inst.get_pin("gnd") + # Find the left most rail on M2 + gnd_pin = None + for pin in rbl_gnd_pins: + if gnd_pin == None or (pin.layer=="metal2" and pin.lx()= self.bitcell.height: + gnd_position = vector(gnd_pin.lx(), 0) + self.add_layout_pin(text="gnd", + layer="metal2", + offset=gnd_position, + height=self.get_pin("bl").uy()) + def add_well_contacts(self): - offset = vector(self.gnd_position.x+ drc["minwidth_metal2"], - self.nmos1.poly_height / 2) + # find right most gnd rail + gnd_pins = self.bitcell.get_pins("gnd") + right_gnd = None + for gnd_pin in gnd_pins: + if right_gnd == None or gnd_pin.lx()>right_gnd.lx(): + right_gnd = gnd_pin + + # Add to the right (first) gnd rail + m1m2_offset = right_gnd.ll() + vector(-0.5*self.m1m2_via.width,self.nmos.poly_height/2) self.add_via(layers=("metal1", "via1", "metal2"), - offset=offset - vector(self.m1m2_via.width / 2, 0), - mirror="MY") + offset=m1m2_offset) + active_offset = right_gnd.ll() + vector(-self.m1m2_via.width,self.nmos.poly_height/2) self.add_contact(layers=("active", "contact", "metal1"), - offset=offset - vector(self.m1m2_via.width, 0), - mirror="MY") - temp = vector(self.m1m2_via.width, - (self.pwell_contact.first_layer_height - self.pwell_contact.second_layer_height) / 2) - offset_implant = offset - temp + vector([drc["implant_to_contact"]]*2).scale(1,-1) + offset=active_offset) + + offset_implant = active_offset + vector([drc["implant_to_contact"]]*2).scale(-1,-1) + implant_width = 2*drc["implant_to_contact"] + self.well_contact.width + implant_height = 2*drc["implant_to_contact"] + self.well_contact.height self.add_rect(layer="pimplant", offset=offset_implant, - width=-(2*drc["implant_to_contact"] + self.pwell_contact.first_layer_width), - height=2*drc["implant_to_contact"] + self.pwell_contact.width) + width=implant_width, + height=implant_height) - offset_well = self.nmos1_position + vector(self.nmos1.width, 0) + offset_well = self.nmos1_position + vector(self.nmos.width, 0) self.add_rect(layer="pwell", offset=offset_well, - width=self.gnd_position.x+ drc["minwidth_metal2"] - offset_well[0], - height=self.nmos1.height + drc["minwidth_poly"]) + width=offset_implant.x + implant_width + drc["well_enclosure_active"] - offset_well.x, + height=self.nmos2_position.y) self.add_rect(layer="vtg", offset=offset_well, - width=self.gnd_position.x+ drc["minwidth_metal2"] - offset_well[0], - height=self.nmos1.height + drc["minwidth_poly"]) + width=offset_implant.x + implant_width + drc["well_enclosure_active"] - offset_well.x, + height=self.nmos2_position.y) - def setup_layout_constants(self): - self.width = self.width = self.bitcell_chars["width"] - self.height = self.height = self.BL_position[1] diff --git a/compiler/single_level_column_mux_array.py b/compiler/single_level_column_mux_array.py index 019cfcda..c5093528 100644 --- a/compiler/single_level_column_mux_array.py +++ b/compiler/single_level_column_mux_array.py @@ -14,28 +14,25 @@ class single_level_column_mux_array(design.design): Array of column mux to read the bitlines through the 6T. """ - def __init__(self, rows, columns, word_size): + def __init__(self, columns, word_size): design.design.__init__(self, "columnmux_array") debug.info(1, "Creating {0}".format(self.name)) - self.rows = rows self.columns = columns self.word_size = word_size self.words_per_row = self.columns / self.word_size - self.row_addr_size = self.decoder_inputs = int(math.log(self.rows, 2)) self.add_pins() self.create_layout() - self.offset_all_coordinates() self.DRC_LVS() def add_pins(self): for i in range(self.columns): - self.add_pin("bl[{0}]".format(i)) - self.add_pin("br[{0}]".format(i)) - for i in range(self.columns / self.words_per_row): - self.add_pin("bl_out[{0}]".format(i * self.words_per_row)) - self.add_pin("br_out[{0}]".format(i * self.words_per_row)) + self.add_pin("bl[{}]".format(i)) + self.add_pin("br[{}]".format(i)) + for i in range(self.word_size): + self.add_pin("bl_out[{}]".format(i)) + self.add_pin("br_out[{}]".format(i)) for i in range(self.words_per_row): - self.add_pin("sel[{0}]".format(i)) + self.add_pin("sel[{}]".format(i)) self.add_pin("gnd") def create_layout(self): @@ -47,226 +44,169 @@ class single_level_column_mux_array(design.design): def add_modules(self): self.mux = single_level_column_mux(name="single_level_column_mux", tx_size=8) - self.single_mux = self.mux self.add_mod(self.mux) # This is not instantiated and used for calculations only. self.m1m2_via = contact(layer_stack=("metal1", "via1", "metal2")) - + self.poly_contact = contact(layer_stack=("poly", "contact", "metal1")) def setup_layout_constants(self): self.column_addr_size = num_of_inputs = int(self.words_per_row / 2) - self.width = (self.columns * self.mux.width) - self.gnd_positions = [] - self.BL_out_positions = [] - self.BR_out_positions = [] - self.BL_positions = [] - self.BR_positions = [] - self.addr_line_positions = [] - - spacing = self.m1m2_via.width + drc['metal1_to_metal1'] - self.height = self.mux.height + spacing + 4 * drc['metal2_to_metal2'] - if (self.words_per_row > 1): - # 1 for BL and another for BR - self.height = self.height + (self.words_per_row + 1) * spacing + self.width = self.columns * self.mux.width + + self.m1_pitch = self.m1m2_via.width + max(drc["metal1_to_metal1"],drc["metal2_to_metal2"]) + # To correct the offset between M1 and M2 via enclosures + self.offset_fix = vector(0,0.5*(drc["minwidth_metal2"]-drc["minwidth_metal1"])) + # one set of metal1 routes for select signals and a pair to interconnect the mux outputs bl/br + # one extra route pitch is to space from the sense amp + self.route_height = (self.words_per_row + 3)*self.m1_pitch + # mux height plus routing signal height plus well spacing at the top + self.height = self.mux.height + self.route_height + drc["pwell_to_nwell"] def create_array(self): - for i in range(self.columns): - name = "XMUX{0}".format(i) - x_off = vector(i * self.mux.width, 0) - self.add_inst(name=name, - mod=self.mux, - offset=x_off) + self.mux_inst = [] - """ draw a vertical m2 rail to extend BL BR & gnd on top of the cell """ - # FIXME: These are just min metal squares, are they needed? - self.add_rect(layer="metal2", - offset=x_off + self.mux.BL_position, - width=drc['minwidth_metal2'], - height=drc['minwidth_metal2']) - self.add_rect(layer="metal2", - offset=x_off + self.mux.BR_position, - width=drc['minwidth_metal2'], - height=drc['minwidth_metal2']) - self.add_rect(layer="metal2", - offset=x_off + self.mux.gnd_position, - width=drc['minwidth_metal2'], - height=drc['minwidth_metal2']) + # For every column, add a pass gate + for col_num in range(self.columns): + name = "XMUX{0}".format(col_num) + x_off = vector(col_num * self.mux.width, self.route_height) + self.mux_inst.append(self.add_inst(name=name, + mod=self.mux, + offset=x_off)) - """ add labels for the column_mux array """ - BL = self.mux.BL_position + vector(i * self.mux.width, 0) - self.BL_positions.append(BL) - self.add_label(text="bl[{0}]".format(i), - layer="metal2", - offset=BL) + offset = self.mux_inst[-1].get_pin("bl").ll() + self.add_layout_pin(text="bl[{}]".format(col_num), + layer="metal2", + offset=offset, + height=self.height-offset.y) - BR = self.mux.BR_position + vector(i * self.mux.width, 0) - self.BR_positions.append(BR) - self.add_label(text="br[{0}]".format(i), - layer="metal2", - offset=BR) + offset = self.mux_inst[-1].get_pin("br").ll() + self.add_layout_pin(text="br[{}]".format(col_num), + layer="metal2", + offset=offset, + height=self.height-offset.y) - gnd = self.mux.gnd_position + vector(i * self.mux.width, 0) - self.gnd_positions.append(gnd) - self.add_label(text="gnd", - layer="metal2", - offset=gnd) + gnd_pins = self.mux_inst[-1].get_pins("gnd") + for gnd_pin in gnd_pins: + # only do even colums to avoid duplicates + offset = gnd_pin.ll() + if col_num % 2 == 0: + self.add_layout_pin(text="gnd", + layer="metal2", + offset=offset.scale(1,0), + height=self.height) + + self.connect_inst(["bl[{}]".format(col_num), + "br[{}]".format(col_num), + "bl_out[{}]".format(int(col_num/self.words_per_row)), + "br_out[{}]".format(int(col_num/self.words_per_row)), + "sel[{}]".format(col_num % self.words_per_row), + "gnd"]) - for i in range(self.word_size): - base =vector(i * self.words_per_row * self.mux.width, 0) - BL_out = base + self.mux.BL_out_position - BR_out = base + self.mux.BR_out_position - self.add_label(text="bl_out[{0}]".format(i * self.words_per_row), - layer="metal2", - offset=BL_out) - self.add_label(text="br_out[{0}]".format(i * self.words_per_row), - layer="metal2", - offset=BR_out) - self.BL_out_positions.append(BL_out) - self.BR_out_positions.append(BR_out) - - if(self.words_per_row == 2): - for i in range(self.columns / 2): - # This will not check that the inst connections match. - self.connect_inst(args=["bl[{0}]".format(2 * i), - "br[{0}]".format(2 * i), - "bl_out[{0}]".format(2 * i), - "br_out[{0}]".format(2 * i), - "sel[{0}]".format(0), "gnd"], - check=False) - # This will not check that the inst connections match. - self.connect_inst(args=["bl[{0}]".format(2 * i + 1), - "br[{0}]".format(2 * i + 1), - "bl_out[{0}]".format(2 * i), - "br_out[{0}]".format(2 * i), - "sel[{0}]".format(1), "gnd"], - check=False) - if(self.words_per_row == 4): - for i in range(self.columns / 4): - # This will not check that the inst connections match. - self.connect_inst(args=["bl[{0}]".format(4 * i), - "br[{0}]".format(4 * i), - "bl_out[{0}]".format(4 * i), - "br_out[{0}]".format(4 * i), - "sel[{0}]".format(0), "gnd"], - check=False) - # This will not check that the inst connections match. - self.connect_inst(args=["bl[{0}]".format(4 * i + 1), - "br[{0}]".format(4 * i + 1), - "bl_out[{0}]".format(4 * i), - "br_out[{0}]".format(4 * i), - "sel[{0}]".format(1), "gnd"], - check=False) - # This will not check that the inst connections match. - self.connect_inst(args=["bl[{0}]".format(4 * i + 2), - "br[{0}]".format(4 * i + 2), - "bl_out[{0}]".format(4 * i), - "br_out[{0}]".format(4 * i), - "sel[{0}]".format(2), "gnd"], - check=False) - # This will not check that the inst connections match. - self.connect_inst(args=["bl[{0}]".format(4 * i + 3), - "br[{0}]".format(4 * i + 3), - "bl_out[{0}]".format(4 * i), - "br_out[{0}]".format(4 * i), - "sel[{0}]".format(3), "gnd"], - check=False) + def add_routing(self): self.add_horizontal_input_rail() self.add_vertical_poly_rail() - self.routing_BL_BR() + self.route_bitlines() def add_horizontal_input_rail(self): - """ HORIZONTAL ADDRESS INPUTS TO THE COLUMN MUX ARRAY """ - if (self.words_per_row > 1): - for j in range(self.words_per_row): - offset = vector(0, -(j + 1) * self.m1m2_via.width - - j * drc['metal1_to_metal1']) - self.add_rect(layer="metal1", - offset=offset, - width=self.mux.width * self.columns, - height=self.m1m2_via.width) - self.addr_line_positions.append(offset) + """ Create address input rails on M1 below the mux transistors """ + for j in range(self.words_per_row): + offset = vector(0, self.route_height - (j+1)*self.m1_pitch) + self.add_layout_pin(text="sel[{}]".format(j), + layer="metal1", + offset=offset, + width=self.mux.width * self.columns, + height=self.m1m2_via.width) def add_vertical_poly_rail(self): - """ VERTICAL POLY METAL EXTENSION AND POLY CONTACT """ - for j1 in range(self.columns): - pattern = math.floor(j1 / self.words_per_row) * self.words_per_row - height = ((self.m1m2_via.width + drc['metal1_to_metal1']) - *(pattern - j1)) - nmos1_poly = self.mux.nmos1_position + self.mux.nmos1.poly_positions[0] - offset = nmos1_poly.scale(1, 0) + vector(j1 * self.mux.width, 0) + """ Connect the poly to the address rails """ + + # Offset to the first transistor gate in the pass gate + nmos_offset = (self.mux.nmos1_position + self.mux.nmos.poly_positions[0]).scale(1,0) + for col in range(self.columns): + # which select bit should this column connect to depends on the position in the word + sel_index = col % self.words_per_row + # Add the column x offset to find the right select bit + gate_offset = nmos_offset + vector(col * self.mux.width , 0) + # height to connect the gate to the correct horizontal row + sel_height = self.get_pin("sel[{}]".format(sel_index)).by() + # use the y offset from the sel pin and the x offset from the gate + offset = vector(gate_offset.x,self.get_pin("sel[{}]".format(sel_index)).by()) self.add_rect(layer="poly", offset=offset, width=drc["minwidth_poly"], - height= height -self.m1m2_via.width) + height=self.route_height - sel_height) - # This is not instantiated and used for calculations only. - poly_contact = contact(layer_stack=("metal1", "contact", "poly")) - offset = offset.scale(1, 0) + vector(0, height - poly_contact.width) + # Add the poly contact with a shift to account for the rotation self.add_contact(layers=("metal1", "contact", "poly"), - offset=offset, - mirror="MX", + offset=offset + vector(self.m1m2_via.height,0), rotate=90) - def routing_BL_BR(self): - """ OUTPUT BIT-LINE CONNECTIONS (BL_OUT, BR_OUT) """ - if (self.words_per_row > 1): - for j in range(self.columns / self.words_per_row): - base = vector(self.mux.width * self.words_per_row * j, - self.m1m2_via.width + drc['metal1_to_metal1']) + def route_bitlines(self): + """ Connect the output bit-lines to form the appropriate width mux """ + for j in range(self.columns): + bl_offset = self.mux_inst[j].get_pin("bl_out").ll() + br_offset = self.mux_inst[j].get_pin("br_out").ll() + + bl_out_offset = bl_offset - vector(0,(self.words_per_row+1)*self.m1_pitch) + br_out_offset = br_offset - vector(0,(self.words_per_row+2)*self.m1_pitch) + + if (j % self.words_per_row) == 0: + # Create the metal1 to connect the n-way mux output from the pass gate + # These will be located below the select lines. Yes, these are M2 width + # to ensure vias are enclosed and M1 min width rules. width = self.m1m2_via.width + self.mux.width * (self.words_per_row - 1) self.add_rect(layer="metal1", - offset=base.scale(1,-self.words_per_row) + self.mux.BL_position.scale(1,0), + offset=bl_out_offset, width=width, - height=-self.m1m2_via.width) + height=drc["minwidth_metal2"]) self.add_rect(layer="metal1", - offset=base.scale(1,-self.words_per_row-1) + self.mux.BR_position.scale(1,0), + offset=br_out_offset, width=width, - height=-self.m1m2_via.width) + height=drc["minwidth_metal2"]) + - height = base.y * (self.words_per_row + 2) + 3 * drc['metal2_to_metal2'] - base = vector(base.x, - height) - self.add_rect(layer="metal2", - offset=base + self.mux.BL_position.scale(1,0), - width=drc['minwidth_metal2'], - height=height) - self.add_rect(layer="metal2", - offset=base + self.mux.BR_position.scale(1,0), - width=drc['minwidth_metal2'], - height=height) - self.add_rect(layer="metal2", - offset=base + self.mux.gnd_position.scale(1,0), - width=drc['minwidth_metal2'], - height=height) + # Extend the bitline output rails and gnd downward on the first bit of each n-way mux + self.add_layout_pin(text="bl_out[{}]".format(int(j/self.words_per_row)), + layer="metal2", + offset=bl_out_offset.scale(1,0), + width=drc['minwidth_metal2'], + height=self.route_height) + self.add_layout_pin(text="br_out[{}]".format(int(j/self.words_per_row)), + layer="metal2", + offset=br_out_offset.scale(1,0), + width=drc['minwidth_metal2'], + height=self.route_height) - for j in range(self.columns): - """ adding vertical metal rails to route BL_out and BR_out vertical rails """ - contact_spacing = self.m1m2_via.width + drc['metal1_to_metal1'] - height = self.words_per_row * contact_spacing + self.m1m2_via.width - offset = vector(self.mux.BL_position.x + self.mux.width * j, 0) - self.add_rect(layer="metal2", - offset=offset, - width=drc['minwidth_metal2'], - height=-height) - offset = offset + vector(self.m1m2_via.height, - height) + # This via is on the right of the wire self.add_via(layers=("metal1", "via1", "metal2"), - offset=offset, + offset=bl_out_offset + vector(self.m1m2_via.height,0), + rotate=90) + # This via is on the left of the wire + self.add_via(layers=("metal1", "via1", "metal2"), + offset= br_out_offset, rotate=90) - offset = vector(self.mux.BR_position.x + self.mux.width * j, 0) - height = height + contact_spacing + else: + self.add_rect(layer="metal2", - offset=offset, + offset=bl_out_offset, width=drc['minwidth_metal2'], - height= - height) - offset = offset + vector(self.m1m2_via.height/2, - height) - layer_diff = (self.m1m2_via.second_layer_width - self.m1m2_via.first_layer_width) + height=self.route_height-bl_out_offset.y) + # This via is on the right of the wire self.add_via(layers=("metal1", "via1", "metal2"), - offset= offset + vector(layer_diff, 0), + offset=bl_out_offset + vector(self.m1m2_via.height,0), + rotate=90) + self.add_rect(layer="metal2", + offset=br_out_offset, + width=drc['minwidth_metal2'], + height=self.route_height-br_out_offset.y) + # This via is on the left of the wire + self.add_via(layers=("metal1", "via1", "metal2"), + offset= br_out_offset, rotate=90) - self.add_label(text="COLUMN_MUX", - layer="text", - offset=[self.width / 2.0, self.height / 2.0]) + + diff --git a/compiler/sram.py b/compiler/sram.py index 45e7ef80..43ff0978 100644 --- a/compiler/sram.py +++ b/compiler/sram.py @@ -18,21 +18,23 @@ class sram(design.design): """ def __init__(self, word_size, num_words, num_banks, name): - mod_list = ["control_logic", "ms_flop_array", "ms_flop", "bitcell"] - for mod_name in mod_list: - config_mod_name = getattr(OPTS.config, mod_name) - class_file = reload(__import__(config_mod_name)) - mod_class = getattr(class_file , config_mod_name) - setattr (self, "mod_"+mod_name, mod_class) + + c = reload(__import__(OPTS.config.control_logic)) + self.mod_control_logic = getattr(c, OPTS.config.control_logic) + + c = reload(__import__(OPTS.config.ms_flop_array)) + self.mod_ms_flop_array = getattr(c, OPTS.config.ms_flop_array) + + c = reload(__import__(OPTS.config.bitcell)) + self.mod_bitcell = getattr(c, OPTS.config.bitcell) + self.bitcell = self.mod_bitcell() + # reset the static duplicate name checker for unit tests # in case we create more than one SRAM import design design.design.name_map=[] - self.ms_flop_chars = self.mod_ms_flop.chars - self.bitcell_chars = self.mod_bitcell.chars - self.word_size = word_size self.num_words = num_words self.num_banks = num_banks @@ -41,75 +43,69 @@ class sram(design.design): self.num_words)) design.design.__init__(self, name) - self.ctrl_positions = {} - + + self.control_size = 6 + self.bank_to_bus_distance = 5*drc["minwidth_metal3"] + self.compute_sizes() self.add_pins() - self.create_layout() self.DRC_LVS() def compute_sizes(self): - """ Computes the required sizes to create the memory """ - self.check_num_banks(self.num_banks) + """ Computes the organization of the memory using bitcell size by trying to make it square.""" + debug.check(self.num_banks in [1,2,4], "Valid number of banks are 1 , 2 and 4.") self.num_words_per_bank = self.num_words/self.num_banks self.num_bits_per_bank = self.word_size*self.num_words_per_bank - self.bank_area = self.bitcell_chars["width"]*\ - self.bitcell_chars["height"]*self.num_bits_per_bank + # Compute the area of the bitcells and estimate a square bank (excluding auxiliary circuitry) + self.bank_area = self.bitcell.width*self.bitcell.height*self.num_bits_per_bank self.bank_side_length = math.sqrt(self.bank_area) - self.tentative_num_cols = int(self.bank_side_length/self.bitcell_chars["width"]) - self.words_per_row = self.cal_words_per_row(self.tentative_num_cols, - self.word_size) - self.tentative_num_rows = self.num_bits_per_bank \ - /(self.words_per_row \ - *self.word_size) - self.words_per_row = self.amend_words_per_row(self.tentative_num_rows, - self.words_per_row) + # Estimate the words per row given the height of the bitcell and the square side length + self.tentative_num_cols = int(self.bank_side_length/self.bitcell.width) + self.words_per_row = self.estimate_words_per_row(self.tentative_num_cols, self.word_size) + + # Estimate the number of rows given the tentative words per row + self.tentative_num_rows = self.num_bits_per_bank / (self.words_per_row*self.word_size) + self.words_per_row = self.amend_words_per_row(self.tentative_num_rows, self.words_per_row) + # Fix the number of columns and rows self.num_cols = self.words_per_row*self.word_size self.num_rows = self.num_words_per_bank/self.words_per_row - + + # Compute the address and bank sizes self.row_addr_size = int(log(self.num_rows, 2)) self.col_addr_size = int(log(self.words_per_row, 2)) - self.bank_addr_size = self.col_addr_size \ - + self.row_addr_size - self.addr_size = self.bank_addr_size + \ - int(math.log(self.num_banks, 2)) + self.bank_addr_size = self.col_addr_size + self.row_addr_size + self.addr_size = self.bank_addr_size + int(math.log(self.num_banks, 2)) - self.control_size = 6 - self.bank_to_bus_distance = 5*drc["minwidth_metal3"] + def estimate_words_per_row(self,tentative_num_cols, word_size): + """This provides a heuristic rounded estimate for the number of words + per row.""" - def check_num_banks(self,num_banks): - if(num_banks != 1 and num_banks != 2 and num_banks != 4): - debug.error("Valid number of banks are 1 , 2 and 4.") - sys.exit(-1) - - def cal_words_per_row(self,tentative_num_cols, word_size): - if(tentative_num_cols < 1.5*word_size): - words_per_row = 1 - elif(tentative_num_cols > 3*word_size): - words_per_row = 4 + if tentative_num_cols < 1.5*word_size: + return 1 + elif tentative_num_cols > 3*word_size: + return 4 else: - words_per_row = 2 - return words_per_row + return 2 def amend_words_per_row(self,tentative_num_rows, words_per_row): + """This picks the number of words per row more accurately by limiting + it to a minimum and maximum. + """ + # Recompute the words per row given a hard max if(tentative_num_rows > 512): - if(tentative_num_rows*words_per_row > 2048): - debug.error("Number of rows exceeds 512") - sys.exit(-1) - words_per_row = words_per_row*tentative_num_rows/512 - + debug.check(tentative_num_rows*words_per_row <= 2048, "Number of words exceeds 2048") + return words_per_row*tentative_num_rows/512 + # Recompute the words per row given a hard min if(tentative_num_rows < 16): - if(tentative_num_rows*words_per_row < 16): - debug.error("minimum number of rows is 16, but given {0}".format( - tentative_num_rows)) - sys.exit(-1) - words_per_row = words_per_row*tentative_num_rows/16 + debug.check(tentative_num_rows*words_per_row >= 16, "Minimum number of rows is 16, but given {0}".format(tentative_num_rows)) + return words_per_row*tentative_num_rows/16 + return words_per_row def add_pins(self): @@ -124,12 +120,14 @@ class sram(design.design): def create_layout(self): """ Layout creation """ + self.create_modules() self.add_modules() self.add_routing() def add_routing(self): """ Route all of the modules """ + if (self.num_banks == 2 or self.num_banks == 4): self.route_2or4_banks() if (self.num_banks == 4): @@ -139,6 +137,7 @@ class sram(design.design): def create_multibank_modules(self): """ Add the multibank address flops and bank decoder """ + self.msf_msb_address = self.mod_ms_flop_array(name="msf_msb_address", columns=self.num_banks/2, word_size=self.num_banks/2) @@ -259,8 +258,8 @@ class sram(design.design): temp.append("ADDR[{0}]".format(i)) if(self.num_banks > 1): temp.append("bank_select[{0}]".format(self.bank_count)) - temp = temp + ["s_en" , "w_en", "tri_en_bar", "tri_en", - "clk_bar", "clk" , "vdd" , "gnd" ] + temp.extend(["s_en", "w_en", "tri_en_bar", "tri_en", + "clk_bar","clk" , "vdd", "gnd"]) self.connect_inst(temp) # Saving control line properties @@ -359,13 +358,13 @@ class sram(design.design): line_gap = 2*drc[m2m] return bits*(line_width + line_gap) - line_gap - def add_control_logic(self, position, mirror): + def add_control_logic(self, position, rotate): """ Add and place control logic """ self.control_position = position self.add_inst(name="control", mod=self.control, offset=self.control_position, - mirror=mirror) + rotate=rotate) temp = ["CSb", "WEb", "OEb", "s_en", "w_en", "tri_en", "tri_en_bar", "clk_bar", "clk", "vdd", "gnd"] self.connect_inst(temp) @@ -377,20 +376,20 @@ class sram(design.design): # FIXME: document loc = vector(- 2 * drc["minwidth_metal3"], self.bank_positions[0].y + self.bank.decoder_position.y - + 2 * drc["minwidth_metal3"]) - self.add_control_logic(loc, "R90") + + 2 * drc["minwidth_metal3"]) + self.add_control_logic(loc, 90) self.width = self.bank.width + self.control.height + 2*drc["minwidth_metal3"] self.height = self.bank.height self.control.CSb_position.rotate_scale(-1,1) self.CSb_position = (self.control.CSb_position.rotate_scale(-1,1) - +self.control_position) + +self.control_position) self.OEb_position = (self.control.OEb_position.rotate_scale(-1,1) - +self.control_position) + +self.control_position) self.WEb_position = (self.control.WEb_position.rotate_scale(-1,1) - +self.control_position) + +self.control_position) self.clk_position = (self.control.clk_position.rotate_scale(-1,1) - +self.control_position) + +self.control_position) for i in range(0, self.word_size): self.add_label(text="DATA[{0}]".format(i), layer="metal3", @@ -404,25 +403,23 @@ class sram(design.design): self.bank_w = self.bank.width self.num_vertical_line = self.bank_addr_size + self.control_size \ - + self.num_banks + self.num_banks/2 + + self.num_banks + self.num_banks/2 self.num_horizontal_line = self.word_size - self.vertical_bus_width = self.calculate_bus_width("metal2", - self.num_vertical_line) - self.horizontal_bus_width = self.calculate_bus_width("metal3", - self.num_horizontal_line) + self.vertical_bus_width = self.calculate_bus_width("metal2", self.num_vertical_line) + self.horizontal_bus_width = self.calculate_bus_width("metal3", self.num_horizontal_line) self.vertical_bus_height = (self.num_banks/2)*(self.bank_h + self.bank_to_bus_distance) \ - + self.horizontal_bus_width + + self.horizontal_bus_width self.horizontal_bus_height = (2 * (self.bank_w + self.bank_to_bus_distance) - + self.vertical_bus_width) + + self.vertical_bus_width) self.vertical_bus_offset = vector(self.bank_w + self.bank_to_bus_distance, self.sram_power_rail_gap) self.horizontal_bus_offset = vector(0, self.bank_h + self.bank_to_bus_distance - + self.sram_power_rail_gap - + self.horizontal_bus_width) + + self.sram_power_rail_gap + + self.horizontal_bus_width) # Vertical bus self.vertical_line_positions = self.create_bus(layer="metal2", @@ -444,7 +441,7 @@ class sram(design.design): self.width = 2*(self.bank_w + self.bank_to_bus_distance) + self.vertical_bus_width self.height = (self.num_banks/2)*(self.bank_h + self.bank_to_bus_distance) \ - + self.horizontal_bus_width + self.sram_power_rail_gap + + self.horizontal_bus_width + self.sram_power_rail_gap # Add Control logic for Bank = 2 and Bank =4 @@ -464,7 +461,7 @@ class sram(design.design): if (self.num_banks == 2): self.control_position = vector(0, control_bus_offset.y + self.ms_flop_chars["width"]) - self.add_control_logic(self.control_position, "R0") + self.add_control_logic(self.control_position, 0) self.CSb_position = self.control_position + self.control.CSb_position self.OEb_position = self.control_position + self.control.OEb_position @@ -494,8 +491,8 @@ class sram(design.design): temp.append("msb{0}".format(i)) temp.append("msb{0}_bar".format(i)) else: - temp = temp + ["bank_select[1]", "bank_select[0]"] - temp = temp + ["clk", "vdd", "gnd"] + temp.extend(["bank_select[1]", "bank_select[0]"]) + temp.extend(["clk", "vdd", "gnd"]) self.connect_inst(temp) self.add_banks_0and1() @@ -575,7 +572,7 @@ class sram(design.design): self.control_position = vector(0, self.msb_decoder_position.y + self.msb_decoder.height) - self.add_control_logic(self.control_position, "R0") + self.add_control_logic(self.control_position, 0) self.CSb_position = self.control_position + self.control.CSb_position self.OEb_position = self.control_position + self.control.OEb_position @@ -738,13 +735,13 @@ class sram(design.design): self.add_via(layers=("metal2", "via2", "metal3"), offset=[bank_side[i].x + drc["minwidth_metal2"], control_side[i].y], - mirror="R90") + rotate=90) elif (self.num_banks == 2 or self.num_banks == 4): for i in range(self.control_size): self.add_via(layers=("metal1", "via1", "metal2"), offset=[self.vertical_line_positions[i].x + drc["minwidth_metal2"], self.control_bus_line_positions[i].y], - mirror="R90") + rotate=90) control_attr = self.bank_property[i] control_side_line_position = (getattr(self.control,control_attr) +self.control_position) @@ -759,7 +756,7 @@ class sram(design.design): offset=[control_side_line_position.x + drc["minwidth_metal2"], self.control_bus_line_positions[i].y], - mirror="R90") + rotate=90) for i in range(self.num_banks/2): # MSB line connections msb_line = self.control_size + self.num_banks/2 - 1 - i @@ -843,7 +840,7 @@ class sram(design.design): height=drc["minwidth_metal1"]) self.add_via(layers=("metal1", "via1", "metal2"), offset=end - vector(0, 0.5 * self.m1m2_via.width), - mirror="R90") + rotate=90) for i in range(4): bank_select_line = self.control_size + 2 + self.bank_addr_size + i msb_decoder_out = (self.msb_decoder_position @@ -943,7 +940,7 @@ class sram(design.design): self.add_via(layers=("metal1", "via1", "metal2"), offset=[control_vdd1_position.x + drc["minwidth_metal2"], control_vdd_supply.y], - mirror="R90") + rotate=90) if (self.control.width > self.bank.width): last_bank = self.num_banks - 1 @@ -964,7 +961,7 @@ class sram(design.design): offset=[control_vdd2_position.x + drc["minwidth_metal2"], control_vdd_supply.y], - mirror="R90") + rotate=90) self.add_layout_pin(text="vdd", layer="metal2", @@ -1078,12 +1075,12 @@ class sram(design.design): offset=[self.sram_bank_left_gnd_positions[0].x + drc["minwidth_metal2"], control_gnd_supply.y], - mirror="R90") + rotate=90) # Control gnd self.add_via(layers=("metal1", "via1", "metal2"), offset=[control_gnd_position.x + drc["minwidth_metal2"], control_gnd_supply.y], - mirror="R90") + rotate=90) self.add_layout_pin(text="gnd", layer="metal2", offset=[control_gnd_position.x, diff --git a/compiler/tests/03_contact_test.py b/compiler/tests/03_contact_test.py index 85acc10a..30d98291 100644 --- a/compiler/tests/03_contact_test.py +++ b/compiler/tests/03_contact_test.py @@ -18,6 +18,7 @@ class contact_test(unittest.TestCase): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + OPTS.check_lvsdrc = False import contact @@ -27,30 +28,22 @@ class contact_test(unittest.TestCase): # Check single 1 x 1 contact" debug.info(2, "1 x 1 {} test".format(stack_name)) - OPTS.check_lvsdrc = False c = contact.contact(layer_stack, (1, 1)) - OPTS.check_lvsdrc = True self.local_check(c) # check vertical array with one in the middle and two ends debug.info(2, "1 x 3 {} test".format(stack_name)) - OPTS.check_lvsdrc = False c = contact.contact(layer_stack, (1, 3)) - OPTS.check_lvsdrc = True self.local_check(c) # check horizontal array with one in the middle and two ends debug.info(2, "3 x 1 {} test".format(stack_name)) - OPTS.check_lvsdrc = False c = contact.contact(layer_stack, (3, 1)) - OPTS.check_lvsdrc = True self.local_check(c) # check 3x3 array for all possible neighbors debug.info(2, "3 x 3 {} test".format(stack_name)) - OPTS.check_lvsdrc = False c = contact.contact(layer_stack, (3, 3)) - OPTS.check_lvsdrc = True self.local_check(c) OPTS.check_lvsdrc = True diff --git a/compiler/tests/03_path_test.py b/compiler/tests/03_path_test.py index 977d2790..a3a7afe8 100644 --- a/compiler/tests/03_path_test.py +++ b/compiler/tests/03_path_test.py @@ -18,6 +18,7 @@ class path_test(unittest.TestCase): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + OPTS.check_lvsdrc = False import path import tech @@ -32,11 +33,9 @@ class path_test(unittest.TestCase): [4 * min_space, 3 * min_space ], [0, 3 * min_space ], [0, 6 * min_space ]] - OPTS.check_lvsdrc = False w = design.design("path_test0") path.path(w,layer_stack, position_list) self.local_check(w) - OPTS.check_lvsdrc = True min_space = 2 * tech.drc["minwidth_metal1"] @@ -51,11 +50,9 @@ class path_test(unittest.TestCase): [-1 * min_space, 4 * min_space], [-1 * min_space, 0]] position_list = [[x+min_space, y+min_space] for x,y in old_position_list] - OPTS.check_lvsdrc = False w = design.design("path_test1") path.path(w,layer_stack, position_list) self.local_check(w) - OPTS.check_lvsdrc = True min_space = 2 * tech.drc["minwidth_metal2"] layer_stack = ("metal2") @@ -69,11 +66,9 @@ class path_test(unittest.TestCase): [-1 * min_space, 4 * min_space], [-1 * min_space, 0]] position_list = [[x-min_space, y-min_space] for x,y in old_position_list] - OPTS.check_lvsdrc = False w = design.design("path_test2") path.path(w, layer_stack, position_list) self.local_check(w) - OPTS.check_lvsdrc = True min_space = 2 * tech.drc["minwidth_metal3"] layer_stack = ("metal3") @@ -88,11 +83,8 @@ class path_test(unittest.TestCase): [-1 * min_space, 0]] # run on the reverse list position_list.reverse() - OPTS.check_lvsdrc = False w = design.design("path_test3") path.path(w, layer_stack, position_list) - OPTS.check_lvsdrc = True - self.local_check(w) # return it back to it's normal state diff --git a/compiler/tests/03_ptx_1finger_nmos_test.py b/compiler/tests/03_ptx_1finger_nmos_test.py index 36719511..edea14c7 100644 --- a/compiler/tests/03_ptx_1finger_nmos_test.py +++ b/compiler/tests/03_ptx_1finger_nmos_test.py @@ -28,10 +28,9 @@ class ptx_test(unittest.TestCase): fet = ptx.ptx(width=tech.drc["minwidth_tx"], mults=1, tx_type="nmos") - # return it back to it's normal state - OPTS.check_lvsdrc = True - self.local_check(fet) + + OPTS.check_lvsdrc = True globals.end_openram() diff --git a/compiler/tests/03_ptx_1finger_pmos_test.py b/compiler/tests/03_ptx_1finger_pmos_test.py index 5f5f0897..8e469730 100644 --- a/compiler/tests/03_ptx_1finger_pmos_test.py +++ b/compiler/tests/03_ptx_1finger_pmos_test.py @@ -28,10 +28,9 @@ class ptx_test(unittest.TestCase): fet = ptx.ptx(width=tech.drc["minwidth_tx"], mults=1, tx_type="pmos") - # return it back to it's normal state - OPTS.check_lvsdrc = True - self.local_check(fet) + + OPTS.check_lvsdrc = True globals.end_openram() diff --git a/compiler/tests/03_ptx_3finger_nmos_test.py b/compiler/tests/03_ptx_3finger_nmos_test.py index 444ef331..0fa64a8c 100644 --- a/compiler/tests/03_ptx_3finger_nmos_test.py +++ b/compiler/tests/03_ptx_3finger_nmos_test.py @@ -28,10 +28,9 @@ class ptx_test(unittest.TestCase): fet = ptx.ptx(width=tech.drc["minwidth_tx"], mults=3, tx_type="nmos") - # return it back to it's normal state - OPTS.check_lvsdrc = True - self.local_check(fet) + + OPTS.check_lvsdrc = True globals.end_openram() def add_mods(self, fet): diff --git a/compiler/tests/03_ptx_3finger_pmos_test.py b/compiler/tests/03_ptx_3finger_pmos_test.py index 8f6e6c7b..96e5ac00 100644 --- a/compiler/tests/03_ptx_3finger_pmos_test.py +++ b/compiler/tests/03_ptx_3finger_pmos_test.py @@ -28,10 +28,9 @@ class ptx_test(unittest.TestCase): fet = ptx.ptx(width=tech.drc["minwidth_tx"], mults=3, tx_type="pmos") - # return it back to it's normal state - OPTS.check_lvsdrc = True - self.local_check(fet) + + OPTS.check_lvsdrc = True globals.end_openram() def add_mods(self, fet): diff --git a/compiler/tests/03_wire_test.py b/compiler/tests/03_wire_test.py index 9f053cca..9c662388 100644 --- a/compiler/tests/03_wire_test.py +++ b/compiler/tests/03_wire_test.py @@ -18,6 +18,7 @@ class wire_test(unittest.TestCase): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + OPTS.check_lvsdrc = False import wire import tech @@ -36,10 +37,8 @@ class wire_test(unittest.TestCase): [-1 * min_space, 4 * min_space], [-1 * min_space, 0]] position_list = [[x-min_space, y-min_space] for x,y in old_position_list] - OPTS.check_lvsdrc = False w = design.design("wire_test1") wire.wire(w, layer_stack, position_list) - OPTS.check_lvsdrc = True self.local_check(w) min_space = 2 * (tech.drc["minwidth_poly"] + @@ -55,10 +54,8 @@ class wire_test(unittest.TestCase): [-1 * min_space, 4 * min_space], [-1 * min_space, 0]] position_list = [[x+min_space, y+min_space] for x,y in old_position_list] - OPTS.check_lvsdrc = False w = design.design("wire_test2") wire.wire(w, layer_stack, position_list) - OPTS.check_lvsdrc = True self.local_check(w) min_space = 2 * (tech.drc["minwidth_metal2"] + @@ -73,10 +70,8 @@ class wire_test(unittest.TestCase): [7 * min_space, 4 * min_space], [-1 * min_space, 4 * min_space], [-1 * min_space, 0]] - OPTS.check_lvsdrc = False w = design.design("wire_test3") wire.wire(w, layer_stack, position_list) - OPTS.check_lvsdrc = True self.local_check(w) @@ -92,10 +87,8 @@ class wire_test(unittest.TestCase): [7 * min_space, 4 * min_space], [-1 * min_space, 4 * min_space], [-1 * min_space, 0]] - OPTS.check_lvsdrc = False w = design.design("wire_test4") wire.wire(w, layer_stack, position_list) - OPTS.check_lvsdrc = True self.local_check(w) min_space = 2 * (tech.drc["minwidth_metal2"] + @@ -111,10 +104,8 @@ class wire_test(unittest.TestCase): [-1 * min_space, 4 * min_space], [-1 * min_space, 0]] position_list.reverse() - OPTS.check_lvsdrc = False w = design.design("wire_test5") wire.wire(w, layer_stack, position_list) - OPTS.check_lvsdrc = True self.local_check(w) min_space = 2 * (tech.drc["minwidth_metal2"] + @@ -130,10 +121,8 @@ class wire_test(unittest.TestCase): [-1 * min_space, 4 * min_space], [-1 * min_space, 0]] position_list.reverse() - OPTS.check_lvsdrc = False w = design.design("wire_test6") wire.wire(w, layer_stack, position_list) - OPTS.check_lvsdrc = True self.local_check(w) # return it back to it's normal state diff --git a/compiler/tests/04_nand_2_test.py b/compiler/tests/04_nand_2_test.py index 0cb1a6a0..36c5944a 100644 --- a/compiler/tests/04_nand_2_test.py +++ b/compiler/tests/04_nand_2_test.py @@ -31,8 +31,9 @@ class nand_2_test(unittest.TestCase): debug.info(2, "Checking 2-input nand gate") tx = nand_2.nand_2(nmos_width=2 * tech.drc["minwidth_tx"]) - OPTS.check_lvsdrc = True self.local_check(tx) + + OPTS.check_lvsdrc = True globals.end_openram() diff --git a/compiler/tests/04_nand_3_test.py b/compiler/tests/04_nand_3_test.py index 15b3a60c..83e7c4a2 100644 --- a/compiler/tests/04_nand_3_test.py +++ b/compiler/tests/04_nand_3_test.py @@ -28,9 +28,9 @@ class nand_3_test(unittest.TestCase): debug.info(2, "Checking 3-input nand gate") tx = nand_3.nand_3(nmos_width=3 * tech.drc["minwidth_tx"]) + self.local_check(tx) OPTS.check_lvsdrc = True - self.local_check(tx) globals.end_openram() def local_check(self, tx): diff --git a/compiler/tests/04_nor_2_test.py b/compiler/tests/04_nor_2_test.py index d45ffd47..17de6906 100644 --- a/compiler/tests/04_nor_2_test.py +++ b/compiler/tests/04_nor_2_test.py @@ -30,8 +30,9 @@ class nor_2_test(unittest.TestCase): debug.info(2, "Checking 2-input nor gate") tx = nor_2.nor_2(nmos_width=2 * tech.drc["minwidth_tx"]) - OPTS.check_lvsdrc = True self.local_check(tx) + + OPTS.check_lvsdrc = True globals.end_openram() def local_check(self, tx): diff --git a/compiler/tests/04_pinv_test.py b/compiler/tests/04_pinv_test.py index e85b0ac3..befafa6e 100644 --- a/compiler/tests/04_pinv_test.py +++ b/compiler/tests/04_pinv_test.py @@ -20,28 +20,24 @@ class pinv_test(unittest.TestCase): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + OPTS.check_lvsdrc = False import pinv import tech - # debug.info(2, "Checking min size inverter") - # OPTS.check_lvsdrc = False - # tx = pinv.pinv(nmos_width=tech.drc["minwidth_tx"], beta=tech.parameter["pinv_beta"]) - # OPTS.check_lvsdrc = True - # self.local_check(tx) - - # debug.info(2, "Checking 2x min size inverter") - # OPTS.check_lvsdrc = False - # tx = pinv.pinv(nmos_width=2 * tech.drc["minwidth_tx"], beta=tech.parameter["pinv_beta"]) - # OPTS.check_lvsdrc = True - # self.local_check(tx) - - debug.info(2, "Checking 5x min size inverter") - OPTS.check_lvsdrc = False - tx = pinv.pinv(nmos_width=5 * tech.drc["minwidth_tx"], beta=tech.parameter["pinv_beta"]) - OPTS.check_lvsdrc = True + debug.info(2, "Checking min size inverter") + tx = pinv.pinv(nmos_width=tech.drc["minwidth_tx"], beta=tech.parameter["pinv_beta"]) self.local_check(tx) + debug.info(2, "Checking 2x min size inverter") + tx = pinv.pinv(nmos_width=2 * tech.drc["minwidth_tx"], beta=tech.parameter["pinv_beta"]) + self.local_check(tx) + + debug.info(2, "Checking 5x min size inverter") + tx = pinv.pinv(nmos_width=5 * tech.drc["minwidth_tx"], beta=tech.parameter["pinv_beta"]) + self.local_check(tx) + + OPTS.check_lvsdrc = True globals.end_openram() def local_check(self, tx): @@ -57,6 +53,10 @@ class pinv_test(unittest.TestCase): os.remove(tempspice) os.remove(tempgds) + # reset the static duplicate name checker for unit tests + import design + design.design.name_map=[] + diff --git a/compiler/tests/04_precharge_test.py b/compiler/tests/04_precharge_test.py new file mode 100644 index 00000000..c4df886a --- /dev/null +++ b/compiler/tests/04_precharge_test.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python2.7 +""" +Run a regresion test on a precharge cell +""" + +import unittest +from testutils import header +import sys,os +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +import debug +import calibre +import sys + +OPTS = globals.OPTS + +class precharge_test(unittest.TestCase): + + def runTest(self): + globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + # we will manually run lvs/drc + OPTS.check_lvsdrc = False + + import precharge + import tech + + debug.info(2, "Checking precharge") + tx = precharge.precharge(name="precharge_driver", ptx_width=tech.drc["minwidth_tx"]) + self.local_check(tx) + + OPTS.check_lvsdrc = True + globals.end_openram() + + def local_check(self, tx): + tempspice = OPTS.openram_temp + "temp.sp" + tempgds = OPTS.openram_temp + "temp.gds" + + tx.sp_write(tempspice) + tx.gds_write(tempgds) + + self.assertFalse(calibre.run_drc(tx.name, tempgds)) + self.assertFalse(calibre.run_lvs(tx.name, tempgds, tempspice)) + + os.remove(tempspice) + os.remove(tempgds) + + +# instantiate a copy of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + header(__file__, OPTS.tech_name) + unittest.main() diff --git a/compiler/tests/04_wordline_driver_test.py b/compiler/tests/04_wordline_driver_test.py index b1a5c470..5f761d4a 100644 --- a/compiler/tests/04_wordline_driver_test.py +++ b/compiler/tests/04_wordline_driver_test.py @@ -28,12 +28,12 @@ class wordline_driver_test(unittest.TestCase): import tech debug.info(2, "Checking driver") - tx = wordline_driver.wordline_driver(name="Wordline_driver", rows=8) + tx = wordline_driver.wordline_driver(rows=8) + self.local_check(tx) OPTS.check_lvsdrc = True - - self.local_check(tx) globals.end_openram() + def local_check(self, tx): tempspice = OPTS.openram_temp + "temp.sp" tempgds = OPTS.openram_temp + "temp.gds" diff --git a/compiler/tests/05_bitcell_array_test.py b/compiler/tests/05_bitcell_array_test.py index 6ec0c7d4..13269939 100644 --- a/compiler/tests/05_bitcell_array_test.py +++ b/compiler/tests/05_bitcell_array_test.py @@ -25,12 +25,11 @@ class array_test(unittest.TestCase): import bitcell_array - debug.info(2, "Testing 3x3 array for 6t_cell") - a = bitcell_array.bitcell_array(name="bitcell_array", cols=3, rows=3) + debug.info(2, "Testing 4x4 array for 6t_cell") + a = bitcell_array.bitcell_array(name="bitcell_array", cols=4, rows=4) + self.local_check(a) OPTS.check_lvsdrc = True - - self.local_check(a) globals.end_openram() def local_check(self, a): diff --git a/compiler/tests/06_hierarchical_decoder_test.py b/compiler/tests/06_hierarchical_decoder_test.py index 36441e24..2f41e024 100644 --- a/compiler/tests/06_hierarchical_decoder_test.py +++ b/compiler/tests/06_hierarchical_decoder_test.py @@ -18,50 +18,32 @@ class hierarchical_decoder_test(unittest.TestCase): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + OPTS.check_lvsdrc = False import hierarchical_decoder import tech debug.info(1, "Testing 4 row sample for hierarchical_decoder") - OPTS.check_lvsdrc = False - a = hierarchical_decoder.hierarchical_decoder(nand2_nmos_width=2 * tech.drc["minwidth_tx"], - nand3_nmos_width=3 * tech.drc["minwidth_tx"], - rows=4) - OPTS.check_lvsdrc = True + a = hierarchical_decoder.hierarchical_decoder(rows=4) self.local_check(a) debug.info(1, "Testing 8 row sample for hierarchical_decoder") - OPTS.check_lvsdrc = False - a = hierarchical_decoder.hierarchical_decoder(nand2_nmos_width=2 * tech.drc["minwidth_tx"], - nand3_nmos_width=3 * tech.drc["minwidth_tx"], - rows=8) - OPTS.check_lvsdrc = True + a = hierarchical_decoder.hierarchical_decoder(rows=8) self.local_check(a) debug.info(1, "Testing 32 row sample for hierarchical_decoder") - OPTS.check_lvsdrc = False - a = hierarchical_decoder.hierarchical_decoder(nand2_nmos_width=2 * tech.drc["minwidth_tx"], - nand3_nmos_width=3 * tech.drc["minwidth_tx"], - rows=32) - OPTS.check_lvsdrc = True + a = hierarchical_decoder.hierarchical_decoder(rows=32) self.local_check(a) debug.info(1, "Testing 128 row sample for hierarchical_decoder") - OPTS.check_lvsdrc = False - a = hierarchical_decoder.hierarchical_decoder(nand2_nmos_width=2 * tech.drc["minwidth_tx"], - nand3_nmos_width=3 * tech.drc["minwidth_tx"], - rows=128) - OPTS.check_lvsdrc = True + a = hierarchical_decoder.hierarchical_decoder(rows=128) self.local_check(a) debug.info(1, "Testing 512 row sample for hierarchical_decoder") - OPTS.check_lvsdrc = False - a = hierarchical_decoder.hierarchical_decoder(nand2_nmos_width=2 * tech.drc["minwidth_tx"], - nand3_nmos_width=3 * tech.drc["minwidth_tx"], - rows=512) - OPTS.check_lvsdrc = True + a = hierarchical_decoder.hierarchical_decoder(rows=512) self.local_check(a) + OPTS.check_lvsdrc = True globals.end_openram() def local_check(self, a): @@ -77,6 +59,11 @@ class hierarchical_decoder_test(unittest.TestCase): os.remove(tempspice) os.remove(tempgds) + # reset the static duplicate name checker for unit tests + import design + design.design.name_map=[] + + # instantiate a copdsay of the class to actually run the test if __name__ == "__main__": (OPTS, args) = globals.parse_args() diff --git a/compiler/tests/06_hierarchical_predecode2x4_test.py b/compiler/tests/06_hierarchical_predecode2x4_test.py index 551a1fb0..8ba872e4 100644 --- a/compiler/tests/06_hierarchical_predecode2x4_test.py +++ b/compiler/tests/06_hierarchical_predecode2x4_test.py @@ -24,12 +24,11 @@ class hierarchical_predecode2x4_test(unittest.TestCase): import hierarchical_predecode2x4 as pre import tech - debug.info(1, "Testing sample for hierarchy_decoder") - a = pre.hierarchical_predecode2x4(nmos_width=2 * tech.drc["minwidth_tx"], - cellname="test_pre2x4") - OPTS.check_lvsdrc = True + debug.info(1, "Testing sample for hierarchy_predecode2x4") + a = pre.hierarchical_predecode2x4() self.local_check(a) + OPTS.check_lvsdrc = True globals.end_openram() def local_check(self, a): diff --git a/compiler/tests/06_hierarchical_predecode3x8_test.py b/compiler/tests/06_hierarchical_predecode3x8_test.py index 50162d31..a765468e 100644 --- a/compiler/tests/06_hierarchical_predecode3x8_test.py +++ b/compiler/tests/06_hierarchical_predecode3x8_test.py @@ -24,12 +24,11 @@ class hierarchical_predecode3x8_test(unittest.TestCase): import hierarchical_predecode3x8 as pre import tech - debug.info(1, "Testing sample for hierarchy_decoder") - a = pre.hierarchical_predecode3x8(nmos_width=3 * tech.drc["minwidth_tx"], - cellname="test_pre3x8") - OPTS.check_lvsdrc = True + debug.info(1, "Testing sample for hierarchy_predecode3x8") + a = pre.hierarchical_predecode3x8() self.local_check(a) + OPTS.check_lvsdrc = True globals.end_openram() def local_check(self, a): diff --git a/compiler/tests/07_single_level_column_mux_test.py b/compiler/tests/07_single_level_column_mux_array_test.py similarity index 65% rename from compiler/tests/07_single_level_column_mux_test.py rename to compiler/tests/07_single_level_column_mux_array_test.py index 490a3497..1d6a0fe6 100644 --- a/compiler/tests/07_single_level_column_mux_test.py +++ b/compiler/tests/07_single_level_column_mux_array_test.py @@ -22,12 +22,20 @@ class single_level_column_mux_test(unittest.TestCase): OPTS.check_lvsdrc = False import single_level_column_mux_array - - debug.info(1, "Testing sample for columnmux_array") - a = single_level_column_mux_array.single_level_column_mux_array( - rows=32, columns=32, word_size=16) - OPTS.check_lvsdrc = True + + debug.info(1, "Testing sample for 2-way column_mux_array") + a = single_level_column_mux_array.single_level_column_mux_array(columns=16, word_size=8) self.local_check(a) + + debug.info(1, "Testing sample for 4-way column_mux_array") + a = single_level_column_mux_array.single_level_column_mux_array(columns=16, word_size=4) + self.local_check(a) + + debug.info(1, "Testing sample for 8-way column_mux_array") + a = single_level_column_mux_array.single_level_column_mux_array(columns=32, word_size=4) + self.local_check(a) + + OPTS.check_lvsdrc = True globals.end_openram() def local_check(self, a): @@ -43,7 +51,9 @@ class single_level_column_mux_test(unittest.TestCase): os.remove(tempspice) os.remove(tempgds) - OPTS.check_lvsdrc = True + # reset the static duplicate name checker for unit tests + import design + design.design.name_map=[] # instantiate a copdsay of the class to actually run the test if __name__ == "__main__": diff --git a/compiler/tests/08_precharge_array_test.py b/compiler/tests/08_precharge_array_test.py index ecdbf704..1b617536 100644 --- a/compiler/tests/08_precharge_array_test.py +++ b/compiler/tests/08_precharge_array_test.py @@ -20,17 +20,16 @@ class precharge_test(unittest.TestCase): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + OPTS.check_lvsdrc = False import precharge_array import tech debug.info(2, "Checking 3 column precharge") - OPTS.check_lvsdrc = False - pc = precharge_array.precharge_array( - name="precharge_array", columns=3, ptx_width=tech.drc["minwidth_tx"], beta=2) - OPTS.check_lvsdrc = True + pc = precharge_array.precharge_array(columns=3, ptx_width=tech.drc["minwidth_tx"], beta=2) self.local_check(pc) + OPTS.check_lvsdrc = True globals.end_openram() def local_check(self, pc): diff --git a/compiler/tests/09_sense_amp_array_test.py b/compiler/tests/09_sense_amp_array_test.py index 561836d4..b237368c 100644 --- a/compiler/tests/09_sense_amp_array_test.py +++ b/compiler/tests/09_sense_amp_array_test.py @@ -20,16 +20,20 @@ class sense_amp_test(unittest.TestCase): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + OPTS.check_lvsdrc = False import sense_amp_array debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=2") - OPTS.check_lvsdrc = False a = sense_amp_array.sense_amp_array(word_size=4, words_per_row=2) - OPTS.check_lvsdrc = True self.local_check(a) + debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=4") + a = sense_amp_array.sense_amp_array(word_size=4, words_per_row=4) + self.local_check(a) + + OPTS.check_lvsdrc = True globals.end_openram() def local_check(self, a): @@ -45,6 +49,11 @@ class sense_amp_test(unittest.TestCase): os.remove(tempspice) os.remove(tempgds) + # reset the static duplicate name checker for unit tests + import design + design.design.name_map=[] + + # instantiate a copy of the class to actually run the test if __name__ == "__main__": (OPTS, args) = globals.parse_args() diff --git a/compiler/tests/10_write_driver_array_test.py b/compiler/tests/10_write_driver_array_test.py index 5c488ae6..81febb34 100644 --- a/compiler/tests/10_write_driver_array_test.py +++ b/compiler/tests/10_write_driver_array_test.py @@ -20,15 +20,19 @@ class write_driver_test(unittest.TestCase): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + OPTS.check_lvsdrc = False import write_driver_array - debug.info(2, "Testing write_driver_array for columns=128, word_size=128") - OPTS.check_lvsdrc = False - a = write_driver_array.write_driver_array(columns=16, word_size=16) - OPTS.check_lvsdrc = True + debug.info(2, "Testing write_driver_array for columns=8, word_size=8") + a = write_driver_array.write_driver_array(columns=8, word_size=8) self.local_check(a) + debug.info(2, "Testing write_driver_array for columns=16, word_size=8") + a = write_driver_array.write_driver_array(columns=16, word_size=8) + self.local_check(a) + + OPTS.check_lvsdrc = True globals.end_openram() def local_check(self, a): @@ -44,6 +48,11 @@ class write_driver_test(unittest.TestCase): os.remove(tempspice) os.remove(tempgds) + # reset the static duplicate name checker for unit tests + import design + design.design.name_map=[] + + # instantiate a copy of the class to actually run the test if __name__ == "__main__": (OPTS, args) = globals.parse_args() diff --git a/compiler/tests/11_ms_flop_array_test.py b/compiler/tests/11_ms_flop_array_test.py index 027c10f0..430d98f4 100644 --- a/compiler/tests/11_ms_flop_array_test.py +++ b/compiler/tests/11_ms_flop_array_test.py @@ -21,16 +21,19 @@ class dff_array_test(unittest.TestCase): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + OPTS.check_lvsdrc = False import ms_flop_array - debug.info(1, "Testing sample for dff_array") - OPTS.check_lvsdrc = False - a = ms_flop_array.ms_flop_array( - name="test1", columns=64, word_size=32) - OPTS.check_lvsdrc = True + debug.info(2, "Testing ms_flop_array for columns=8, word_size=8") + a = ms_flop_array.ms_flop_array(columns=8, word_size=8) self.local_check(a) + debug.info(2, "Testing ms_flop_array for columns=16, word_size=8") + a = ms_flop_array.ms_flop_array(columns=16, word_size=8) + self.local_check(a) + + OPTS.check_lvsdrc = True globals.end_openram() def local_check(self, a): @@ -46,6 +49,11 @@ class dff_array_test(unittest.TestCase): os.remove(tempspice) os.remove(tempgds) + # reset the static duplicate name checker for unit tests + import design + design.design.name_map=[] + + # instantiate a copdsay of the class to actually run the test if __name__ == "__main__": diff --git a/compiler/tests/15_tri_gate_array_test.py b/compiler/tests/12_tri_gate_array_test.py similarity index 76% rename from compiler/tests/15_tri_gate_array_test.py rename to compiler/tests/12_tri_gate_array_test.py index 1b7c8440..3e4afce4 100644 --- a/compiler/tests/15_tri_gate_array_test.py +++ b/compiler/tests/12_tri_gate_array_test.py @@ -23,11 +23,15 @@ class tri_gate_array_test(unittest.TestCase): import tri_gate_array - debug.info(1, "Testing sample for tri_gate_array") - a = tri_gate_array.tri_gate_array(columns=16, word_size=16) - OPTS.check_lvsdrc = True + debug.info(1, "Testing tri_gate_array for columns=8, word_size=8") + a = tri_gate_array.tri_gate_array(columns=8, word_size=8) self.local_check(a) + debug.info(1, "Testing tri_gate_array for columns=16, word_size=8") + a = tri_gate_array.tri_gate_array(columns=16, word_size=8) + self.local_check(a) + + OPTS.check_lvsdrc = True globals.end_openram() def local_check(self, a): @@ -43,6 +47,11 @@ class tri_gate_array_test(unittest.TestCase): os.remove(tempspice) os.remove(tempgds) + # reset the static duplicate name checker for unit tests + import design + design.design.name_map=[] + + # instantiate a copdsay of the class to actually run the test if __name__ == "__main__": (OPTS, args) = globals.parse_args() diff --git a/compiler/tests/16_replica_bitline_test.py b/compiler/tests/13_replica_bitline_test.py similarity index 95% rename from compiler/tests/16_replica_bitline_test.py rename to compiler/tests/13_replica_bitline_test.py index 6e0d943e..31efc6c3 100644 --- a/compiler/tests/16_replica_bitline_test.py +++ b/compiler/tests/13_replica_bitline_test.py @@ -27,10 +27,10 @@ class replica_bitline_test(unittest.TestCase): import replica_bitline debug.info(2, "Testing RBL") - a = replica_bitline.replica_bitline("chain", 13) - OPTS.check_lvsdrc = True + a = replica_bitline.replica_bitline(13) self.local_check(a) + OPTS.check_lvsdrc = True globals.end_openram() def local_check(self, a): diff --git a/compiler/tests/14_logic_effort_dc_test.py b/compiler/tests/14_delay_chain_test.py similarity index 87% rename from compiler/tests/14_logic_effort_dc_test.py rename to compiler/tests/14_delay_chain_test.py index 0ce27871..6439dc7a 100644 --- a/compiler/tests/14_logic_effort_dc_test.py +++ b/compiler/tests/14_delay_chain_test.py @@ -16,21 +16,20 @@ OPTS = globals.get_opts() #@unittest.skip("SKIPPING 14_delay_chain_test") -class logic_effort_dc_test(unittest.TestCase): +class delay_chain_test(unittest.TestCase): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) # we will manually run lvs/drc OPTS.check_lvsdrc = False - import logic_effort_dc + import delay_chain debug.info(2, "Testing delay_chain") - a = logic_effort_dc.logic_effort_dc( - "chain", stage_list=[4, 4, 4, 4, 4]) - OPTS.check_lvsdrc = True + a = delay_chain.delay_chain(fanout_list=[4, 4, 4, 4]) self.local_check(a) + OPTS.check_lvsdrc = True globals.end_openram() def local_check(self, a): diff --git a/compiler/tests/13_control_logic_test.py b/compiler/tests/16_control_logic_test.py similarity index 100% rename from compiler/tests/13_control_logic_test.py rename to compiler/tests/16_control_logic_test.py index 8d308ace..85385c89 100644 --- a/compiler/tests/13_control_logic_test.py +++ b/compiler/tests/16_control_logic_test.py @@ -26,9 +26,9 @@ class control_logic_test(unittest.TestCase): debug.info(1, "Testing sample for control_logic") a = control_logic.control_logic(num_rows=128) - OPTS.check_lvsdrc = True self.local_check(a) + OPTS.check_lvsdrc = True globals.end_openram() def local_check(self, a): diff --git a/compiler/tests/19_bank_test.py b/compiler/tests/19_bank_test.py index bfeeef61..b12a2ff3 100644 --- a/compiler/tests/19_bank_test.py +++ b/compiler/tests/19_bank_test.py @@ -25,16 +25,23 @@ class bank_test(unittest.TestCase): import bank - # override these from the config file - OPTS.word_size=8 - OPTS.num_words=128 - OPTS.num_banks=1 - - debug.info(1, "Testing sample 8bit, 64word BANK") - a = bank.bank(word_size=OPTS.num_words, num_words=OPTS.num_words, words_per_row=2, num_banks=OPTS.num_banks, name="test_sram1") - OPTS.check_lvsdrc = True + debug.info(1, "No column mux") + a = bank.bank(word_size=4, num_words=64, words_per_row=2, num_banks=1, name="test_sram1") self.local_check(a) + debug.info(1, "Two way column mux") + a = bank.bank(word_size=4, num_words=64, words_per_row=2, num_banks=1, name="test_sram2") + self.local_check(a) + + debug.info(1, "Four way column mux") + a = bank.bank(word_size=4, num_words=64, words_per_row=4, num_banks=1, name="test_sram3") + self.local_check(a) + + debug.info(1, "Eight way column mux") + a = bank.bank(word_size=2, num_words=64, words_per_row=8, num_banks=1, name="test_sram4") + self.local_check(a) + + OPTS.check_lvsdrc = True globals.end_openram() def local_check(self, a): diff --git a/compiler/tests/20_sram_1bank_test.py b/compiler/tests/20_sram_1bank_test.py index 261fa315..473d39e7 100644 --- a/compiler/tests/20_sram_1bank_test.py +++ b/compiler/tests/20_sram_1bank_test.py @@ -27,9 +27,9 @@ class sram_1bank_test(unittest.TestCase): debug.info(1, "Testing sample 8bit, 64word SRAM, 1 bank") a = sram.sram(word_size=8, num_words=128, num_banks=1, name="test_sram1") - OPTS.check_lvsdrc = True self.local_check(a) + OPTS.check_lvsdrc = True globals.end_openram() def local_check(self, a): diff --git a/compiler/tests/20_sram_2bank_test.py b/compiler/tests/20_sram_2bank_test.py index ce0e1c07..cb0d1636 100644 --- a/compiler/tests/20_sram_2bank_test.py +++ b/compiler/tests/20_sram_2bank_test.py @@ -27,9 +27,9 @@ class sram_2bank_test(unittest.TestCase): debug.info(1, "Testing sample 8bit, 128word SRAM, 2 banks") a = sram.sram(word_size=8, num_words=128, num_banks=2, name="test_sram1") - OPTS.check_lvsdrc = True self.local_check(a) + OPTS.check_lvsdrc = True globals.end_openram() def local_check(self, a): diff --git a/compiler/tests/20_sram_4bank_test.py b/compiler/tests/20_sram_4bank_test.py index 8603cd8a..82ecba75 100644 --- a/compiler/tests/20_sram_4bank_test.py +++ b/compiler/tests/20_sram_4bank_test.py @@ -27,9 +27,9 @@ class sram_4bank_test(unittest.TestCase): debug.info(1, "Testing sample 8bit, 128word SRAM, 4 banks") a = sram.sram(word_size=8, num_words=128, num_banks=4, name="test_sram1") - OPTS.check_lvsdrc = True self.local_check(a) + OPTS.check_lvsdrc = True globals.end_openram() def local_check(self, a): diff --git a/compiler/tests/config_20_freepdk45.py b/compiler/tests/config_20_freepdk45.py index 01b397c2..9f92fae9 100644 --- a/compiler/tests/config_20_freepdk45.py +++ b/compiler/tests/config_20_freepdk45.py @@ -22,6 +22,7 @@ write_driver_array = "write_driver_array" tri_gate = "tri_gate" tri_gate_array = "tri_gate_array" wordline_driver = "wordline_driver" +replica_bitline = "replica_bitline" replica_bitcell = "replica_bitcell" bitcell = "bitcell" -delay_chain = "logic_effort_dc" +delay_chain = "delay_chain" diff --git a/compiler/tests/config_20_scn3me_subm.py b/compiler/tests/config_20_scn3me_subm.py index 7de9d27d..8bcb7027 100644 --- a/compiler/tests/config_20_scn3me_subm.py +++ b/compiler/tests/config_20_scn3me_subm.py @@ -22,6 +22,7 @@ write_driver_array = "write_driver_array" tri_gate = "tri_gate" tri_gate_array = "tri_gate_array" wordline_driver = "wordline_driver" +replica_bitline = "replica_bitline" replica_bitcell = "replica_bitcell" bitcell = "bitcell" -delay_chain = "logic_effort_dc" +delay_chain = "delay_chain" diff --git a/compiler/tri_gate.py b/compiler/tri_gate.py index d5b728c9..138953c6 100644 --- a/compiler/tri_gate.py +++ b/compiler/tri_gate.py @@ -10,16 +10,22 @@ class tri_gate(design.design): netlist should be available in the technology library. """ - pins = ["in", "en", "en_bar", "out", "gnd", "vdd"] - chars = utils.auto_measure_libcell(pins, "tri_gate", GDS["unit"], layer["boundary"]) + pin_names = ["in", "en", "en_bar", "out", "gnd", "vdd"] + (width,height) = utils.get_libcell_size("tri_gate", GDS["unit"], layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "tri_gate", GDS["unit"], layer["boundary"]) - def __init__(self, name): + unique_id = 1 + + def __init__(self, name=""): + if name=="": + name = "tri{0}".format(tri_gate.unique_id) + tri_gate.unique_id += 1 design.design.__init__(self, name) debug.info(2, "Create tri_gate object") - - self.width = tri_gate.chars["width"] - self.height = tri_gate.chars["height"] + self.width = tri_gate.width + self.height = tri_gate.height + self.pin_map = tri_gate.pin_map def delay(self, slew, load=0.0): from tech import spice diff --git a/compiler/tri_gate_array.py b/compiler/tri_gate_array.py index 6fa86e4f..c71fc4bf 100644 --- a/compiler/tri_gate_array.py +++ b/compiler/tri_gate_array.py @@ -16,119 +16,113 @@ class tri_gate_array(design.design): c = reload(__import__(OPTS.config.tri_gate)) self.mod_tri_gate = getattr(c, OPTS.config.tri_gate) - self.tri_gate_chars = self.mod_tri_gate.chars + self.tri = self.mod_tri_gate("tri_gate") + self.add_mod(self.tri) self.columns = columns self.word_size = word_size + + self.words_per_row = self.columns / self.word_size + self.width = (self.columns / self.words_per_row) * self.tri.width + self.height = self.tri.height + self.create_layout() self.DRC_LVS() def create_layout(self): """generate layout """ - self.add_modules() - self.setup_layout_constants() self.add_pins() - self.create_write_array() - self.add_metal_rails() - self.add_labels() + self.create_array() + self.add_layout_pins() def add_pins(self): """create the name of pins depend on the word size""" for i in range(self.word_size): - self.add_pin("IN[{0}]".format(i)) + self.add_pin("in[{0}]".format(i)) for i in range(self.word_size): - self.add_pin("OUT[{0}]".format(i)) + self.add_pin("out[{0}]".format(i)) for pin in ["en", "en_bar", "vdd", "gnd"]: self.add_pin(pin) - def setup_layout_constants(self): - """caculate the size of tri gate array""" - self.words_per_row = self.columns / self.word_size - self.width = (self.columns / self.words_per_row) * self.tri.width - self.height = self.tri.height - self.tri_gate_positions = [] - self.vdd_positions = [] - self.gnd_positions = [] - self.in_positions = [] - self.out_positions = [] - - def add_modules(self): - """instantiation of a tri gate""" - self.tri = self.mod_tri_gate("tri_gate") - self.add_mod(self.tri) - - def create_write_array(self): + def create_array(self): """add tri gate to the array """ - for i in range(self.word_size): - mirror = "R0" - if (i % 2 == 0): - name = "Xtri_gate{0}".format(i) - x_off = i * self.tri.width * self.words_per_row + self.tri_inst = {} + for i in range(0,self.columns,self.words_per_row): + name = "Xtri_gate{0}".format(i) + if (i % 2 == 0 or self.words_per_row > 1): + base = vector(i*self.tri.width, 0) + mirror = "R0" else: - name = "Xtri_gate{0}".format(i) - if (self.words_per_row == 1): - x_off = (i + 1) * self.tri.width * self.words_per_row - mirror = "MY" - else: - x_off = i * self.tri.width * self.words_per_row - self.add_inst(name=name, - mod=self.tri, - offset=[x_off, 0], - mirror = mirror) - self.connect_inst(["in[{0}]".format(i), - "out[{0}]".format(i), + base = vector((i+1)*self.tri.width, 0) + mirror = "MY" + self.tri_inst[i]=self.add_inst(name=name, + mod=self.tri, + offset=base, + mirror=mirror) + self.connect_inst(["in[{0}]".format(i/self.words_per_row), + "out[{0}]".format(i/self.words_per_row), "en", "en_bar", "vdd", "gnd"]) - def add_metal_rails(self): - """Connect en en_bar and vdd together """ - correct = vector(0, 0.5 * drc["minwidth_metal1"]) - width = (self.tri.width * self.columns - - (self.words_per_row - 1) * self.tri.width) - self.add_rect(layer="metal1", - offset=(self.tri_gate_chars["en"] - correct).scale(0, 1), - width=width, - height=drc['minwidth_metal1']) - self.add_rect(layer="metal1", - offset=(self.tri_gate_chars["en_bar"] - correct).scale(0, 1), - width=width, - height=drc['minwidth_metal1']) - self.add_rect(layer="metal1", - offset=(self.tri_gate_chars["vdd"] - correct).scale(0, 1), - width=width, - height=drc['minwidth_metal1']) - def add_labels(self): - """add label for pins""" - for i in range(self.word_size): - if (i % 2 == 0 or self.words_per_row > 1): - x_off = i * self.tri.width * self.words_per_row - dir_vector = vector(1,1) - else: - x_off = (i + 1) * self.tri.width * self.words_per_row - dir_vector = vector(-1,1) + def add_layout_pins(self): + + for i in range(0,self.columns,self.words_per_row): - pin_offset={} - for pin in ["en", "en_bar", "vdd", "gnd", "in", "out"]: - pin_offset[pin] = vector(x_off, 0) + dir_vector.scale(self.tri_gate_chars[pin]) + # Avoid duplicate pins by only doing even columns + for gnd_pin in self.tri_inst[i].get_pins("gnd"): + if i%2 == 0 and gnd_pin.layer=="metal2": + self.add_layout_pin(text="gnd", + layer="metal2", + offset=gnd_pin.ll(), + width=gnd_pin.width(), + height=gnd_pin.height()) - for pin in ["en", "en_bar", "vdd"]: - self.add_label(text=pin, - layer="metal1", - offset=pin_offset[pin]) - self.add_label(text="gnd", - layer="metal2", - offset=pin_offset["gnd"]) - self.add_label(text="in[{0}]".format(i), - layer="metal2", - offset=pin_offset["in"]) - self.add_label(text="out[{0}]".format(i), - layer="metal2", - offset=pin_offset["out"]) + in_pin = self.tri_inst[i].get_pin("in") + self.add_layout_pin(text="in[{0}]".format(i/self.words_per_row), + layer="metal2", + offset=in_pin.ll(), + width=in_pin.width(), + height=in_pin.height()) + + out_pin = self.tri_inst[i].get_pin("out") + self.add_layout_pin(text="out[{0}]".format(i/self.words_per_row), + layer="metal2", + offset=out_pin.ll(), + width=out_pin.width(), + height=out_pin.height()) + + + + width = self.tri.width * self.columns - (self.words_per_row - 1) * self.tri.width + en_pin = self.tri_inst[0].get_pin("en") + self.add_layout_pin(text="en", + layer="metal1", + offset=en_pin.ll().scale(0, 1), + width=width, + height=drc["minwidth_metal1"]) + + enbar_pin = self.tri_inst[0].get_pin("en_bar") + self.add_layout_pin(text="en_bar", + layer="metal1", + offset=enbar_pin.ll().scale(0, 1), + width=width, + height=drc["minwidth_metal1"]) + + vdd_pin = self.tri_inst[0].get_pin("vdd") + self.add_layout_pin(text="vdd", + layer="metal1", + offset=vdd_pin.ll().scale(0, 1), + width=width, + height=drc["minwidth_metal1"]) + + for gnd_pin in self.tri_inst[0].get_pins("gnd"): + if gnd_pin.layer=="metal1": + self.add_layout_pin(text="gnd", + layer="metal1", + offset=gnd_pin.ll().scale(0, 1), + width=width, + height=drc["minwidth_metal1"]) - self.vdd_positions.append(pin_offset["vdd"]) - self.gnd_positions.append(pin_offset["gnd"]) - self.in_positions.append(pin_offset["in"]) - self.out_positions.append(pin_offset["out"]) def delay(self, slew, load=0.0): result = self.tri.delay(slew = slew, load = load) diff --git a/compiler/utils.py b/compiler/utils.py index 257313b9..0a469a7f 100644 --- a/compiler/utils.py +++ b/compiler/utils.py @@ -2,6 +2,8 @@ import os import gdsMill import tech import globals +from vector import vector +from pin_layout import pin_layout OPTS = globals.OPTS @@ -20,14 +22,18 @@ def snap_to_grid(offset): out_offset = [xoff, yoff] return out_offset - -def gds_pin_center(gdsPin): +def pin_center(boundary): """ - This returns the center of a pin shape + This returns the center of a pin shape in the vlsiLayout border format. """ - boundary = gdsPin[2] return [0.5 * (boundary[0] + boundary[2]), 0.5 * (boundary[1] + boundary[3])] +def pin_rect(boundary): + """ + This returns a LL,UR point pair. + """ + return [vector(boundary[0],boundary[1]),vector(boundary[2],boundary[3])] + def auto_measure_libcell(pin_list, name, units, layer): """ Open a GDS file and find the pins in pin_list as text on a given layer. @@ -45,7 +51,49 @@ def auto_measure_libcell(pin_list, name, units, layer): [cell["width"], cell["height"]] = measure_result for pin in pin_list: - cell[str(pin)] = gds_pin_center(cell_vlsi.getPinShapeByLabel(str(pin))) + (name,layer,boundary)=cell_vlsi.getPinShapeByLabel(str(pin)) + cell[str(pin)] = pin_center(boundary) + return cell + + + +def get_libcell_size(name, units, layer): + """ + Open a GDS file and return the library cell size from either the + bounding box or a border layer. + """ + cell_gds = OPTS.openram_tech + "gds_lib/" + str(name) + ".gds" + cell_vlsi = gdsMill.VlsiLayout(units=units) + reader = gdsMill.Gds2reader(cell_vlsi) + reader.loadFromFile(cell_gds) + + cell = {} + measure_result = cell_vlsi.getLayoutBorder(layer) + if measure_result == None: + measure_result = cell_vlsi.measureSize(name) + # returns width,height + return measure_result + + +def get_libcell_pins(pin_list, name, units, layer): + """ + Open a GDS file and find the pins in pin_list as text on a given layer. + Return these as a rectangle layer pair for each pin. + """ + cell_gds = OPTS.openram_tech + "gds_lib/" + str(name) + ".gds" + cell_vlsi = gdsMill.VlsiLayout(units=units) + reader = gdsMill.Gds2reader(cell_vlsi) + reader.loadFromFile(cell_gds) + + cell = {} + for pin in pin_list: + cell[str(pin)]=[] + label_list=cell_vlsi.getPinShapeByLabel(str(pin)) + for label in label_list: + (name,layer,boundary)=label + rect = pin_rect(boundary) + # this is a list because other cells/designs may have must-connect pins + cell[str(pin)].append(pin_layout(pin, rect, layer)) return cell diff --git a/compiler/wire.py b/compiler/wire.py index 782793e9..b76a6216 100644 --- a/compiler/wire.py +++ b/compiler/wire.py @@ -41,10 +41,8 @@ class wire(path): self.horiz_layer_width = drc["minwidth_{0}".format(horiz_layer)] via_connect = contact(self.layer_stack, (1, 1)) - self.node_to_node = [drc["minwidth_" + str(self.horiz_layer_name)] \ - + via_connect.width, - drc["minwidth_" + str(self.horiz_layer_name)] \ - + via_connect.height] + self.node_to_node = [drc["minwidth_" + str(self.horiz_layer_name)] + via_connect.width, + drc["minwidth_" + str(self.horiz_layer_name)] + via_connect.height] # create a 1x1 contact def create_vias(self): diff --git a/compiler/wordline_driver.py b/compiler/wordline_driver.py index 10ef1984..b28ce8c6 100644 --- a/compiler/wordline_driver.py +++ b/compiler/wordline_driver.py @@ -15,8 +15,8 @@ class wordline_driver(design.design): Generates the wordline-driver to drive the bitcell """ - def __init__(self, name, rows): - design.design.__init__(self, name) + def __init__(self, rows): + design.design.__init__(self, "wordline_driver") self.rows = rows self.add_pins() @@ -26,11 +26,11 @@ class wordline_driver(design.design): def add_pins(self): # inputs to wordline_driver. for i in range(self.rows): - self.add_pin("decode_out[{0}]".format(i)) + self.add_pin("in[{0}]".format(i)) # Outputs from wordline_driver. for i in range(self.rows): self.add_pin("wl[{0}]".format(i)) - self.add_pin("clk") + self.add_pin("en") self.add_pin("vdd") self.add_pin("gnd") @@ -40,12 +40,11 @@ class wordline_driver(design.design): self.create_layout() def add_layout(self): - self.inv = pinv(nmos_width=drc["minwidth_tx"], - beta=parameter["pinv_beta"]) + self.inv = pinv() self.add_mod(self.inv) - self.NAND2 = nand_2(nmos_width=2*drc["minwidth_tx"]) - self.add_mod(self.NAND2) + self.nand2 = nand_2() + self.add_mod(self.nand2) @@ -53,87 +52,80 @@ class wordline_driver(design.design): def offsets_of_gates(self): self.x_offset0 = 2 * drc["minwidth_metal1"] + 5 * drc["metal1_to_metal1"] self.x_offset1 = self.x_offset0 + self.inv.width - self.x_offset2 = self.x_offset1 + self.NAND2.width + self.x_offset2 = self.x_offset1 + self.nand2.width self.width = self.x_offset2 + self.inv.width self.height = self.inv.height * self.rows - # Defining offset postions - self.decode_out_positions = [] - self.clk_positions = [] - self.WL_positions = [] - self.vdd_positions = [] - self.gnd_positions = [] - def create_layout(self): - # Clk connection - self.add_rect(layer="metal1", - offset=[drc["minwidth_metal1"] + 2 * drc["metal1_to_metal1"], - 2 * drc["minwidth_metal1"]], - width=drc["minwidth_metal1"], - height=self.height + 4*drc["minwidth_metal1"]) - self.clk_positions.append([drc["minwidth_metal1"] + 2*drc["metal1_to_metal1"], - self.height]) - self.add_label(text="clk", - layer="metal1", - offset=self.clk_positions[0]) - + # Wordline enable connection + self.add_layout_pin(text="en", + layer="metal2", + offset=[drc["minwidth_metal1"] + 2 * drc["metal1_to_metal1"],0], + width=drc["minwidth_metal2"], + height=self.height) + + self.add_layout_pin(text="gnd", + layer="metal1", + offset=[0, -0.5*drc["minwidth_metal1"]], + width=self.x_offset0, + height=drc["minwidth_metal1"]) + for row in range(self.rows): - name_inv1 = "Wordline_driver_inv_clk%d" % (row) - name_nand = "Wordline_driver_nand%d" % (row) - name_inv2 = "Wordline_driver_inv%d" % (row) + name_inv1 = "wl_driver_inv_en{}".format(row) + name_nand = "wl_driver_nand{}".format(row) + name_inv2 = "wl_driver_inv{}".format(row) - # Extend vdd and gnd of Wordline_driver - yoffset = (row + 1) * self.inv.height - 0.5 * drc["minwidth_metal2"] - self.add_rect(layer="metal2", - offset=[0, yoffset], - width=self.x_offset0, - height=drc["minwidth_metal2"]) - - self.add_via(layers=("metal1", "via1", "metal2"), - offset=[drc["minwidth_metal1"], yoffset], - mirror="R90") - self.add_via(layers=("metal1", "via1", "metal2"), - offset=[self.x_offset0 + drc["minwidth_metal1"], - yoffset], - mirror="R90") - inv_nand2B_connection_height = (abs(self.inv.Z_position.y - - self.NAND2.B_position.y) - + drc["minwidth_metal1"]) + inv_nand2B_connection_height = (abs(self.inv.get_pin("Z").ll().y + - self.nand2.get_pin("B").ll().y) + + drc["minwidth_metal1"]) if (row % 2): y_offset = self.inv.height*(row + 1) - name_inv1_offset = [self.x_offset0, y_offset] - nand2_offset=[self.x_offset1, y_offset] - inv2_offset=[self.x_offset2, y_offset] inst_mirror = "MX" cell_dir = vector(0,-1) m1tm2_rotate=270 m1tm2_mirror="R0" else: y_offset = self.inv.height*row - name_inv1_offset = [self.x_offset0, y_offset] - nand2_offset=[self.x_offset1, y_offset] - inv2_offset=[self.x_offset2, y_offset] inst_mirror = "R0" cell_dir = vector(0,1) m1tm2_rotate=90 m1tm2_mirror="MX" + name_inv1_offset = [self.x_offset0, y_offset] + nand2_offset=[self.x_offset1, y_offset] + inv2_offset=[self.x_offset2, y_offset] + base_offset = vector(self.width, y_offset) + + # Extend vdd and gnd of wordline_driver + yoffset = (row + 1) * self.inv.height - 0.5 * drc["minwidth_metal1"] + if (row % 2): + pin_name = "gnd" + else: + pin_name = "vdd" + + self.add_layout_pin(text=pin_name, + layer="metal1", + offset=[0, yoffset], + width=self.x_offset0, + height=drc["minwidth_metal1"]) + + # add inv1 based on the info above self.add_inst(name=name_inv1, mod=self.inv, offset=name_inv1_offset, mirror=inst_mirror ) - self.connect_inst(["clk", "clk_bar[{0}]".format(row), + self.connect_inst(["en", "en_bar[{0}]".format(row), "vdd", "gnd"]) # add nand 2 self.add_inst(name=name_nand, - mod=self.NAND2, + mod=self.nand2, offset=nand2_offset, mirror=inst_mirror) - self.connect_inst(["decode_out[{0}]".format(row), - "clk_bar[{0}]".format(row), + self.connect_inst(["in[{0}]".format(row), + "en_bar[{0}]".format(row), "net[{0}]".format(row), "vdd", "gnd"]) # add inv2 @@ -146,73 +138,65 @@ class wordline_driver(design.design): "vdd", "gnd"]) # clk connection - clk_offset= [drc["minwidth_metal1"] + 2 * drc["metal1_to_metal1"], - y_offset + cell_dir.y * self.inv.A_position.y] + clk_offset= vector(drc["minwidth_metal1"] + 2 * drc["metal1_to_metal1"], + y_offset + cell_dir.y * self.inv.get_pin("A").by()) self.add_rect(layer="metal1", offset=clk_offset, width=self.x_offset0 - 2*drc["metal1_to_metal1"], height=cell_dir.y *drc["minwidth_metal1"]) + self.add_via(layers=("metal1", "via1", "metal2"), + offset=clk_offset) + # first inv to nand2 B inv_to_nand2B_offset = [self.x_offset1 - drc["minwidth_metal1"], - y_offset + cell_dir.y * self.NAND2.B_position.y] + y_offset + cell_dir.y * self.nand2.get_pin("B").by()] self.add_rect(layer="metal1", offset=inv_to_nand2B_offset, width=drc["minwidth_metal1"], height=cell_dir.y*inv_nand2B_connection_height) # Nand2 out to 2nd inv nand2_to_2ndinv_offset =[self.x_offset2, - y_offset + cell_dir.y * self.NAND2.Z_position.y] + y_offset + cell_dir.y * self.nand2.get_pin("Z").by()] self.add_rect(layer="metal1", offset=nand2_to_2ndinv_offset, width=drc["minwidth_metal1"], height=cell_dir.y * drc["minwidth_metal1"]) - # nand2 A connection + + # connect the decoder input pin to nand2 A + input_offset=vector(0, y_offset + cell_dir.y*self.nand2.get_pin("A").by()) + mid_via_offset = vector(clk_offset.x,input_offset.y) + vector(drc["minwidth_metal2"]+drc["metal2_to_metal2"],0) + # must under the clk line in M1 + self.add_layout_pin(text="in[{0}]".format(row), + layer="metal1", + offset=input_offset, + width=mid_via_offset.x+drc["minwidth_metal1"], + height=cell_dir.y*drc["minwidth_metal1"]) + self.add_via(layers=("metal1", "via1", "metal2"), + offset=mid_via_offset, + mirror=inst_mirror) + # now connect to the nand2 A self.add_rect(layer="metal2", - offset=[0, y_offset + cell_dir.y * self.NAND2.A_position.y], - width=self.x_offset1, + offset=mid_via_offset, + width=self.x_offset1-mid_via_offset.x, height=cell_dir.y*drc["minwidth_metal2"]) self.add_via(layers=("metal1", "via1", "metal2"), offset=[self.x_offset1, - y_offset + cell_dir.y * self.NAND2.A_position.y], + y_offset + cell_dir.y * self.nand2.get_pin("A").by()], rotate=m1tm2_rotate, mirror=m1tm2_mirror) - self.add_via(layers=("metal1", "via1", "metal2"), - offset=[0, - y_offset +cell_dir.y*self.NAND2.A_position.y], - mirror=inst_mirror) + # output each WL on the right + wl_offset = base_offset + self.inv.get_pin("Z").ll().scale(cell_dir) + self.add_layout_pin(text="wl[{0}]".format(row), + layer="metal1", + offset=wl_offset, + width=drc["minwidth_metal1"]*cell_dir.y, + height=drc["minwidth_metal1"]*cell_dir.y) - base_offset = vector(self.width, y_offset) - decode_out_offset = base_offset.scale(0,1)+self.NAND2.A_position.scale(cell_dir) - wl_offset = base_offset + self.inv.Z_position.scale(cell_dir) - vdd_offset = base_offset + self.inv.vdd_position.scale(cell_dir) - gnd_offset = base_offset + self.inv.gnd_position.scale(cell_dir) - - self.add_label(text="decode_out[{0}]".format(row), - layer="metal2", - offset=decode_out_offset) - self.add_rect(layer="metal1", - offset=wl_offset, - width=drc["minwidth_metal1"]*cell_dir.y, - height=drc["minwidth_metal1"]*cell_dir.y) - self.add_label(text="wl[{0}]".format(row), - layer="metal1", - offset=wl_offset) - self.add_label(text="gnd", - layer="metal1", - offset=gnd_offset) - self.add_label(text="vdd", - layer="metal1", - offset=vdd_offset) - - self.decode_out_positions.append(decode_out_offset) - self.WL_positions.append(wl_offset) - self.vdd_positions.append(vdd_offset) - self.gnd_positions.append(gnd_offset) def delay(self, slew, load=0): - # decode_out -> net - decode_t_net = self.NAND2.delay(slew, self.inv.input_load()) + # decode -> net + decode_t_net = self.nand2.delay(slew, self.inv.input_load()) # net -> wl net_t_wl = self.inv.delay(decode_t_net.slew, load) @@ -221,4 +205,4 @@ class wordline_driver(design.design): return result def input_load(self): - return self.NAND2.input_load() + return self.nand2.input_load() diff --git a/compiler/write_driver.py b/compiler/write_driver.py index 88f97cd5..1df03a50 100644 --- a/compiler/write_driver.py +++ b/compiler/write_driver.py @@ -11,13 +11,15 @@ class write_driver(design.design): the technology library. """ - pins = ["din", "BL", "BR", "en", "gnd", "vdd"] - chars = utils.auto_measure_libcell(pins, "write_driver", GDS["unit"], layer["boundary"]) + pin_names = ["din", "BL", "BR", "en", "gnd", "vdd"] + (width,height) = utils.get_libcell_size("write_driver", GDS["unit"], layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "write_driver", GDS["unit"], layer["boundary"]) def __init__(self, name): design.design.__init__(self, name) debug.info(2, "Create write_driver object") - self.width = write_driver.chars["width"] - self.height = write_driver.chars["height"] + self.width = write_driver.width + self.height = write_driver.height + self.pin_map = write_driver.pin_map diff --git a/compiler/write_driver_array.py b/compiler/write_driver_array.py index 9dea6459..c1a57a53 100644 --- a/compiler/write_driver_array.py +++ b/compiler/write_driver_array.py @@ -17,126 +17,106 @@ class write_driver_array(design.design): c = reload(__import__(OPTS.config.write_driver)) self.mod_write_driver = getattr(c, OPTS.config.write_driver) - self.write_driver_chars = self.mod_write_driver.chars + self.driver = self.mod_write_driver("write_driver") + self.add_mod(self.driver) self.columns = columns self.word_size = word_size self.words_per_row = columns / word_size + self.width = self.columns * self.driver.width + self.height = self.height = self.driver.height + self.add_pins() self.create_layout() self.DRC_LVS() def add_pins(self): - for i in range(self.word_size): - self.add_pin("data_in[{0}]".format(i)) - if (self.words_per_row == 1): - for i in range(self.word_size): - self.add_pin("bl[{0}]".format(i)) - self.add_pin("br[{0}]".format(i)) - else: - for i in range(self.word_size): - self.add_pin("bl_out[{0}]".format(i * self.words_per_row)) - self.add_pin("br_out[{0}]".format(i * self.words_per_row)) + for i in range(0,self.columns,self.words_per_row): + self.add_pin("data[{0}]".format(i/self.words_per_row)) + self.add_pin("bl[{0}]".format(i)) + self.add_pin("br[{0}]".format(i)) self.add_pin("wen") self.add_pin("vdd") self.add_pin("gnd") def create_layout(self): - self.add_write_driver_module() - self.setup_layout_constants() self.create_write_array() - self.add_metal_rails() - self.add_labels() - self.offset_all_coordinates() - - def add_write_driver_module(self): - self.driver = self.mod_write_driver("write_driver") - self.add_mod(self.driver) - - def setup_layout_constants(self): - self.width = self.columns * self.driver.width - self.height = self.height = self.driver.height - self.gnd_positions = [] - self.vdd_positions = [] - self.wen_positions = [] - self.BL_out_positions = [] - self.BR_out_positions = [] - self.driver_positions = [] - self.Data_in_positions = [] + self.add_layout_pins() + #self.offset_all_coordinates() def create_write_array(self): - for i in range(self.word_size): - name = "Xwrite_driver%d" % i - x_off = (i* self.driver.width * self.words_per_row) - self.driver_positions.append(vector(x_off, 0)) + for i in range(0,self.columns,self.words_per_row): + name = "Xwrite_driver{}".format(i) + if (i % 2 == 0 or self.words_per_row>1): + base = vector(i * self.driver.width,0) + mirror = "R0" + else: + base = vector((i+1) * self.driver.width,0) + mirror = "MY" + self.add_inst(name=name, mod=self.driver, - offset=[x_off, 0]) - if (self.words_per_row == 1): - self.connect_inst(["data_in[{0}]".format(i), - "bl[{0}]".format(i), - "br[{0}]".format(i), - "wen", "vdd", "gnd"]) + offset=base, + mirror=mirror) + self.connect_inst(["data[{0}]".format(i/self.words_per_row), + "bl[{0}]".format(i/self.words_per_row), + "br[{0}]".format(i/self.words_per_row), + "wen", "vdd", "gnd"]) + + + def add_layout_pins(self): + bl_pin = self.driver.get_pin("BL") + br_pin = self.driver.get_pin("BR") + din_pin = self.driver.get_pin("din") + + for i in range(0,self.columns,self.words_per_row): + if (i % 2 == 0 or self.words_per_row > 1): + base = vector(i*self.driver.width, 0) + x_dir = 1 else: - self.connect_inst(["data_in[{0}]".format(i), - "bl_out[{0}]".format(i * self.words_per_row), - "br_out[{0}]".format(i * self.words_per_row), - "wen", "vdd", "gnd"]) + base = vector((i+1)*self.driver.width, 0) + x_dir = -1 + + bl_offset = base + bl_pin.ll().scale(x_dir,1) + br_offset = base + br_pin.ll().scale(x_dir,1) + din_offset = base + din_pin.ll().scale(x_dir,1) + - def add_metal_rails(self): - base = vector(0, - 0.5*drc["minwidth_metal1"]) - self.add_rect(layer="metal1", - offset=base + vector(self.write_driver_chars["en"]).scale(0, 1), - width=self.width - (self.words_per_row - 1) * self.driver.width, - height=drc['minwidth_metal1']) - self.add_rect(layer="metal1", - offset=base + vector(self.write_driver_chars["vdd"]).scale(0, 1), - width=self.width, - height=drc['minwidth_metal1']) - self.add_rect(layer="metal1", - offset=base + vector(self.write_driver_chars["gnd"]).scale(0, 1), - width=self.width, - height=drc['minwidth_metal1']) + self.add_layout_pin(text="data[{0}]".format(i/self.words_per_row), + layer="metal2", + offset=din_offset, + width=x_dir*din_pin.width(), + height=din_pin.height()) + self.add_layout_pin(text="bl[{0}]".format(i/self.words_per_row), + layer="metal2", + offset=bl_offset, + width=x_dir*bl_pin.width(), + height=bl_pin.height()) + + self.add_layout_pin(text="br[{0}]".format(i/self.words_per_row), + layer="metal2", + offset=br_offset, + width=x_dir*br_pin.width(), + height=br_pin.height()) + - def add_labels(self): - for i in range(self.word_size): - base = vector(i * self.driver.width * self.words_per_row, 0) - BL_offset = base + self.write_driver_chars["BL"] - BR_offset = base + self.write_driver_chars["BR"] - - self.add_label(text="data_in[{0}]".format(i), - layer="metal2", - offset=base + self.write_driver_chars["din"]) - if (self.words_per_row == 1): - self.add_label(text="bl[{0}]".format(i), - layer="metal2", - offset=BL_offset) - self.add_label(text="br[{0}]".format(i), - layer="metal2", - offset=BR_offset) - else: - self.add_label(text="bl_out[{0}]".format(i*self.words_per_row), - layer="metal2", - offset=BL_offset) - self.add_label(text="br_out[{0}]".format(i*self.words_per_row), - layer="metal2", - offset=BR_offset) - self.BL_out_positions.append(BL_offset) - self.BR_out_positions.append(BR_offset) - self.Data_in_positions.append(base + self.write_driver_chars["din"]) - - base = vector(0, - 0.5 * drc["minwidth_metal1"]) - self.add_label(text="wen", - layer="metal1", - offset=base + vector(self.write_driver_chars["en"]).scale(0,1)) - self.add_label(text="vdd", - layer="metal1", - offset=base + vector(self.write_driver_chars["vdd"]).scale(0,1)) - self.add_label(text="gnd", - layer="metal1", - offset=base + vector(self.write_driver_chars["gnd"]).scale(0,1)) - self.wen_positions.append(base + vector(self.write_driver_chars["en"]).scale(0,1)) - self.vdd_positions.append(base + vector(self.write_driver_chars["vdd"]).scale(0,1)) - self.gnd_positions.append(base + vector(self.write_driver_chars["gnd"]).scale(0,1)) + self.add_layout_pin(text="wen", + layer="metal1", + offset=self.driver.get_pin("en").ll().scale(0,1), + width=self.width - (self.words_per_row - 1) * self.driver.width, + height=drc['minwidth_metal1']) + + self.add_layout_pin(text="vdd", + layer="metal1", + offset=self.driver.get_pin("vdd").ll().scale(0,1), + width=self.width, + height=drc['minwidth_metal1']) + + self.add_layout_pin(text="gnd", + layer="metal1", + offset=self.driver.get_pin("gnd").ll().scale(0,1), + width=self.width, + height=drc['minwidth_metal1']) + diff --git a/technology/freepdk45/gds_lib/cell_6t.gds b/technology/freepdk45/gds_lib/cell_6t.gds index 077e567bb57ffda15f6db77efb9ff172008f74f2..5dc120b33d36a1b91570740572b61c854c072f88 100644 GIT binary patch literal 22528 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�=| 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 diff --git a/technology/freepdk45/gds_lib/tri_gate.gds b/technology/freepdk45/gds_lib/tri_gate.gds index 7bd632bdb009849879341cce45611e1c3aa2a130..45eadfbe142601ce381c504ed6184c47719cbfed 100644 GIT binary patch literal 12288 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 MGjK5j>(9Xd0Vxbn8~^|S diff --git a/technology/freepdk45/tech/tech.py b/technology/freepdk45/tech/tech.py index 4b1ba2e5..53308bcb 100644 --- a/technology/freepdk45/tech/tech.py +++ b/technology/freepdk45/tech/tech.py @@ -85,7 +85,7 @@ drc["minwidth_tx"]=0.09 drc["minlength_channel"] = 0.05 #well rules -drc["pwell_enclose_nwell"] = 0.225 +drc["pwell_to_nwell"] = 0.225 drc["minwidth_well"] = 0.2 #poly rules diff --git a/technology/scn3me_subm/gds_lib/cell_6t.gds b/technology/scn3me_subm/gds_lib/cell_6t.gds index 8c04e9c1227b3d563f1bd9ffff8aa5fc98e7041d..56b9d041a5e09263f91c22f982b6dadd512be5ba 100644 GIT binary patch literal 8192 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 Pdw^aq#vFFIZPNY*@4I8K diff --git a/technology/scn3me_subm/gds_lib/ms_flop.gds b/technology/scn3me_subm/gds_lib/ms_flop.gds index ce7fdd3acb727eb7a08d30af80336e3c1978ddd0..48edf50a5a07f58621524af7b7e3fbe637f17b3b 100644 GIT binary patch literal 20480 zcmeI3Tg+ZX6^6%BehOA76)Bji81aly1r-fq(F!%9Hd>EVEKsFNm8w)RqBb5;t08KX zc!)KriV?I?RMZ-O)F>hWVl->Dc!AONd}}{`hu<|H7hck&JmlT$-D}P4 z+4Iewz1tSrvbNCHK4@7xD*kU(JEEQ1p3#nOE8_3b?J@22wsP6Rwl}=-f?Yc!a+PPlzc&Nm$I`tcF>@O!iU zQaxDsv)_*0kUajTtY4}}9fiNO!=h7(hvcRGQaxDuA9roW!%uhbzA@>;9OsWSObv3*8lXo(=XM7rJuaSL44FN)#v$%$N#Jy znO~|0%lwSLGuP($d+x^cOZ8ysCogdjAN5Q1d4A&YKkt^zFV%x({-e%l?d(mh!56jG zj{DM>>cP@)UU<$Gtv&dLF{xuXUiu%qJMVkAd28PPl6hb_Ui#^O;^Awe@0kCkda(2} zzYq^Ej&tCb>cP_g(1WeLEZPpw+n)U=)q{oKc{fSD%U+%BfydwE@xFia@cT!-j324q zYd`(=y2b3j^P>&izfwI|)Sve)@!ZE-k4wK)50-w$2YDbq>X+)h{QBU9Q3Lf$&NCeE z<|m%Gi(>54A5uM7`iW<}z)K=G<439oOF!d=d~o}@Iew&iu<+;jc+ILL`Ig1Fk?QsM z)t97h`d?mrg2$`;dEXGv{qx>!*?y@WEb7mG+qpaWwm1jZPpSt?Ki7>oh>!ZE`aD1J z_;;<#{8ByG%}+f3ci)xspHvT)e#Q;`1wU|1_McP_mVWx3c=)|@(=XM7rJwaJ^}-LX z&$Xyj4;FsMO_F*UfAW&sJ>K`fcU#6$FV|11_u5atedL$fe;>Ukub)&87WLht{g@PFd6%rDi0Wq$Gz4?p$KoJXa4u=KCl+1jV$cNc!;H0`gZp2q2TMPBiG%p4U#idZ6OVuIp3E=RgJpik z-`CE_@%Q!Z>6hxk(obIEAU^7s>ht`>R&z5KZN%dgiciu}SQPru!`CHwE^SLXPU>cOJ^ zyuQS7o$--hs?YNikN;OIGrv?1mid`CiHE;FJ=-DGgIz!I__^-PM^Zgl`0e*3KCYiU zaIMEHf7VMp*WtDuIsT-2u$%wp1DOXO{V&yPf9C!D+T~aT$lw{7)M1Kel%0ow3h~ulKsQsccP_A;txLlP&a<59-Q<)akBo&d@qg~*n5%_^>qEj zbDmAn2kiMs^Q-KlbNF{?pf|U#bU7|BBOEd;ULLgY0)5v1d&6VCkpd&bcD{ z?}gES{C-IFVCiSw!8nI6j~bpBYY3@6GybRgpZvtbS48dPm+HYXKmE=)hZlyIJ$I=- zGybRY({JzGlKpr2_1S+?Jy_;nz1Z4&R_5;p&yBQ0ss~Fy%vX}?sTd45^q=Q#oYSGJ~Ks?R(>O!?`*Z$+Eobuzp}q5qt=lgnC#yvKlSkkF5jQ~J5oK^^%IYucbU8s zlj_0J&$^a)$onMxQaxDuSp(sLCvMJpLaGN#KlcOqAn&E{NcCXXf7*fc^IizQR1cPZ z+D$x+_W&cmR1cPZ#x?DQ{64K-98*2m_4^*@e!kzC8b7RGd5;76J)3>MGaN7Tvj!$F ze94EheyJWT{XAdOU+|STWdBL^VCm<&pC4Yxemd7rss~Fy*Nu1>`(Pu#R1cPZ+YR3o z^DN&4Ncv$o-t`kt+>ZP5{*~&%($9TMJbd3(>6hxk((iSLyVm8sEEx~O@vfhE;@lUP z>cP^_ea}8DoY)T?-3RJ3`?FKycg@c6{wVvBTz|=R9FCXyInF*JWM2@!RG-;joXXF* zAs(_HgkP!$%lzE;v>Wb=KH&bB>cOs`c>LdpF~IMvR1cPZe%FbI*F_)Tm+HaN&wWcg zbYD@b2TMP{mF^?DuPD`nrJua+!~J-3_P$WhJ1W+eJd;ZGV3{93@sN9W@~?WZ>&Jti-)r(q^S>htPnokM=V%aQ6c>z^h4M?C&m=ZxAf)q`dI`XK8a+AlfJaJ=hxoy7Mr z^qEu-cKyVY=Xo*S$S>7{rJsJMpCCT^U#idRKfgoayNvtseae#l$4C8q4?+8-`pocP@ayQvf6qy18SUj6K6ke~P3v|p+R%l!Hv>m9DYLTdG)i-BERp2rFyW;uMe``qWzNd49B~E;)&b4C)+R8gIz!I_`h~W=9lWh zuAg}PtXJrNsU9r-^gI0o@zMWMeO~|ZK9T&q7i9jC>ND>Lm)yU^} z_H}5#8IVqL44FN)#v%oKbZatHl$yw2fO+C9+CWS-j{x<9_;$x7H!9W+4A&D z^?*Yiqdjb4XedhhZlKUt2|406>#5wRw z^+Dn#J@>^_lN`rt&lH*pG)l*`4E0st3FIi6cKg>X+*C{5&V)|K(ljm+CXm z&r|i|`|aK7zjb~3rFyViKkorJ5AOx2U#k0lU`4(kn96^|o_zL&>?N=dCwV3wj+gl# zac<6^ka?5e56S#F9Pj#>Cy8U;#4pu*^Cv#$Nyxm3U#bVY`I#s2GjHOT>b?0BU(Amq z|5?%h_@#QVo1b}(^Dyt>m+HOw4dyoX<^_vSx*%yW==55H6o zcH7T9ho5;5zf|wdfB2Z^AoCu6sotCa@G;Lp<~{sUy*K~ii}`KjXWqjv)q~ykGtY4z z<~{sUy*K~iW1fP{Tll4VZ~nr^JO!Dz@JsdH{DqHs3f{Ut{ZhR*f8k@Eg3Mdwm+H=6 zD|7xjvwFYDe6@W4=w0QT-WvbQzr$SdU(fiRBl~TKd?@$bnBSPEqHu z-u8S)E7gNtKk=OBT~}s)sUGb5`E~;TCvQo=RG;~#V(R+gyDHiZuicP-sUGa+|Ng%8 z^DXRScef_hgIz!IjvLy~b%*LR<7diGUg99*hWt`}#((nU_+g%8oe1$ByJ1ZA;NXZc*yfTV@7hm;dtq1jwK#G^77Umeczbs!P3w9nKL1M$owtUgIz!I`1u}?_YP7$ zSo*1#c=*)Q(=XM7rQdc#eC$(5`eQg=`k6O~ho@eZ`K5ZW^qUvrcP@aJl}}G*Ty+`XD!u(rJs3&c*r-zjAN-DEdAu=+Yrbz0{Nx-%r_%b z^9SRNc*r*z_@#QV%un6K!%O4drGBX%Ed9)TJX^pIuFpE8`ph%ORQ+6c;^Bv59FkwE z2h04cW8UEz1@76K^OsZ)cKyWT=ikyXe@XRV>8D=e;nlBBzf=#Fe%lT4F@H(=VK`p; znYW0CUx;f+{Zc(x`ppaRF^(ku;dtq1+!7DJdPDl9da&y!9{;{o*^g2^So*1tIEat= zORCTF6OaG8o!S3VJ=o1pJpS**HJ}|*Jy`mgw}^*)>w;gZ2TMQke4_$?dPm;>QaxDu zx$lXGi|exgrFyXRbKmfe1@ex8^@CIomVW9c9^QU+jvuKWEdA6?JpA1~S-(^dPWn%o zT)$B_`*e_N!1_z72TMP7bI(E6{oFrNJy`k~ujgEmcOsmTZ}*ayraf1)q|y8Ia#M+|vxp#P+Lu=LY^d>aM%mYq7J z`ph>|Q`et*+2etXciJ!2gJpi|=HLFo-&~dTOLhNE;9)ULm$wK0JkSFV^uPl>@c-+9 F{{Xv6>V*IR literal 19302 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 UpAw(m&1Z84HfLaS23ls|UwYUkivR!s literal 4896 zcmbuDJB(FT6owaf?p&^T!Qskl%qSQ@g_(gtFo=&yh$M#a(1al&h6FGWAR$O_Bo-DH zQ(!_#VWI_vi4+tT7L-#MLrFnlL1|%OK|x`m!S&6$KWCpa>+=Nt=3u5GP8-1A+u%I%nr|)t=U&`=49xix3^cCJ;DFz(SJA1 zcCYeaS1TT#3tjZ~)W4<5AMdUEG5q5A@z6!rGybTvKOgmr#GzlJ>#2XT%5V1KZL|IN z%vP_fcw|@TqPJ)Ltz%}d9yELNkBV>HGD8==J@rqd`FZdC-@-3a54z}j)=&K}{~h&< z)PpX%p8Chr{3}Mx4o;Z)7(M2v_s!5nZ_oPMznD#rnQi{I;>X|kWN}^e_M(6NPvI9g z&4gccJ?p1V<}US%uRM(UMb}gRRNDWSJ}^7vW4-cv#ZSC;bkWF)@u7>2{j)ZH{rNb1_;#FOkUG#s*R%PllU(dQB5}|~*Ry^+ zv%^{k&gV`(@1B5}|~*NcA63eFFa zIEo8hPyO4|^}{*G{e!-XdoINJCpPXMH9y~J^4Iu&;#4obpXhov|7%~z+M&*LFGpuj z8?Pe0k`7<-X;8ka$P)~|h@x&Msk z$D;h&cSY(@UC{Mxe%1*-`Y%#Hx>)iPpS4H-MdA$dF3|OCe)d=H{^S?&ql>O*{nSZb z{33DC#ZrFicSk#CFP<*E$9VYT?|t#r>{H(Jyq(d` v`HQuzzG|whg!0qPqg^@O$hzn~Up*e}%u}NI7%k(i6Q7^D9C?>|m2LYUM2sD2 diff --git a/technology/scn3me_subm/tech/tech.py b/technology/scn3me_subm/tech/tech.py index 6c379e67..40eb4c78 100755 --- a/technology/scn3me_subm/tech/tech.py +++ b/technology/scn3me_subm/tech/tech.py @@ -70,7 +70,7 @@ drc["minwidth_tx"] = 1.2 drc["minlength_channel"] = 0.6 #well rules -drc["pwell_enclose_nwell"] = 0 +drc["pwell_to_nwell"] = 0 drc["minwidth_well"] = 3.6 #poly rules