From 4a58f09c1cdedb4220214aac9d964e3dbd352182 Mon Sep 17 00:00:00 2001 From: mrg Date: Mon, 5 Oct 2020 10:52:56 -0700 Subject: [PATCH] Use 4x16 decoder with dual port bitcell in tests. --- compiler/custom/and4_dec.py | 2 +- compiler/modules/hierarchical_decoder.py | 127 +++++++++++++++--- compiler/pgates/pnand4.py | 11 +- compiler/tests/04_pnand4_test.py | 5 - .../tests/06_hierarchical_decoder_test.py | 14 +- 5 files changed, 124 insertions(+), 35 deletions(-) diff --git a/compiler/custom/and4_dec.py b/compiler/custom/and4_dec.py index 9c68f78b..211e4ce4 100644 --- a/compiler/custom/and4_dec.py +++ b/compiler/custom/and4_dec.py @@ -122,7 +122,7 @@ class and4_dec(design.design): width=pin.width(), height=pin.height()) - for pin_name in ["A", "B", "C"]: + for pin_name in ["A", "B", "C", "D"]: pin = self.nand_inst.get_pin(pin_name) self.add_layout_pin_rect_center(text=pin_name, layer=pin.layer, diff --git a/compiler/modules/hierarchical_decoder.py b/compiler/modules/hierarchical_decoder.py index 21aa1c89..51a77bc6 100644 --- a/compiler/modules/hierarchical_decoder.py +++ b/compiler/modules/hierarchical_decoder.py @@ -24,13 +24,14 @@ class hierarchical_decoder(design.design): self.pre2x4_inst = [] self.pre3x8_inst = [] + self.pre4x16_inst = [] b = factory.create(module_type="bitcell") self.cell_height = b.height self.num_outputs = num_outputs self.num_inputs = math.ceil(math.log(self.num_outputs, 2)) - (self.no_of_pre2x4, self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs) + (self.no_of_pre2x4, self.no_of_pre3x8, self.no_of_pre4x16)=self.determine_predecodes(self.num_inputs) self.create_netlist() if not OPTS.netlist_only: @@ -86,25 +87,37 @@ class hierarchical_decoder(design.design): height=self.cell_height) self.add_mod(self.pre3_8) + self.pre4_16 = factory.create(module_type="hierarchical_predecode4x16", + height=self.cell_height) + self.add_mod(self.pre4_16) + 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, 3:8 and 4:16 pre-decoders + needed based on the number of inputs + """ if (num_inputs == 2): - return (1, 0) + return (1, 0, 0) elif (num_inputs == 3): - return(0, 1) + return(0, 1, 0) elif (num_inputs == 4): - return(2, 0) + return(2, 0, 0) elif (num_inputs == 5): - return(1, 1) + return(1, 1, 0) elif (num_inputs == 6): - return(3, 0) + return(3, 0, 0) elif (num_inputs == 7): - return(2, 1) + return(2, 1, 0) elif (num_inputs == 8): - return(1, 2) + return(1, 2, 0) elif (num_inputs == 9): - return(0, 3) + return(0, 3, 0) + elif (num_inputs == 10): + return(0, 2, 1) + elif (num_inputs == 11): + return(0, 1, 2) + elif (num_inputs == 12): + return(0, 0, 3) else: debug.error("Invalid number of inputs for hierarchical decoder", -1) @@ -131,12 +144,19 @@ class hierarchical_decoder(design.design): index = index + 1 self.predec_groups.append(lines) + for i in range(self.no_of_pre4x16): + lines = [] + for j in range(16): + lines.append(index) + index = index + 1 + self.predec_groups.append(lines) + def setup_layout_constants(self): """ Calculate the overall 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 + self.total_number_of_predecoder_outputs = 4 * self.no_of_pre2x4 + 8 * self.no_of_pre3x8 + 16 * self.no_of_pre4x16 else: self.total_number_of_predecoder_outputs = 0 debug.error("Not enough rows ({}) for a hierarchical decoder. Non-hierarchical not supported yet.".format(self.num_inputs), @@ -144,17 +164,20 @@ class hierarchical_decoder(design.design): # Calculates height and width of pre-decoder, # FIXME: Update with 4x16 - if self.no_of_pre3x8 > 0 and self.no_of_pre2x4 > 0: - self.predecoder_width = max(self.pre3_8.width, self.pre2_4.width) - elif self.no_of_pre3x8 > 0: - self.predecoder_width = self.pre3_8.width - else: - self.predecoder_width = self.pre2_4.width + self.predecoder_width = 0 + if self.no_of_pre2x4 > 0: + self.predecoder_width = max(self.predecoder_width, self.pre2_4.width) + if self.no_of_pre3x8 > 0: + self.predecoder_width = max(self.predecoder_width, self.pre3_8.width) + if self.no_of_pre4x16 > 0: + self.predecoder_width = max(self.predecoder_width, self.pre4_16.width) # How much space between each predecoder self.predecoder_spacing = 2 * self.and2.height - self.predecoder_height = self.pre2_4.height * self.no_of_pre2x4 + self.pre3_8.height * self.no_of_pre3x8 \ - + (self.no_of_pre2x4 + self.no_of_pre3x8 - 1) * self.predecoder_spacing + self.predecoder_height = self.pre2_4.height * self.no_of_pre2x4 \ + + self.pre3_8.height * self.no_of_pre3x8 \ + + self.pre4_16.height * self.no_of_pre4x16 \ + + (self.no_of_pre2x4 + self.no_of_pre3x8 + self.no_of_pre4x16 - 1) * self.predecoder_spacing # Inputs to cells are on input layer # Outputs from cells are on output layer @@ -192,6 +215,8 @@ class hierarchical_decoder(design.design): min_x = min(min_x, self.pre2x4_inst[0].lx()) if self.no_of_pre3x8 > 0: min_x = min(min_x, self.pre3x8_inst[0].lx()) + if self.no_of_pre4x16 > 0: + min_x = min(min_x, self.pre4x16_inst[0].lx()) input_offset=vector(min_x - self.input_routing_width, 0) input_bus_names = ["addr_{0}".format(i) for i in range(self.num_inputs)] @@ -232,6 +257,20 @@ class hierarchical_decoder(design.design): self.route_input_bus(decoder_offset, input_offset) + for pre_num in range(self.no_of_pre4x16): + for i in range(4): + index = pre_num * 4 + i + self.no_of_pre3x8 * 3 + self.no_of_pre2x4 * 2 + + input_pos = self.input_bus["addr_{}".format(index)].center() + + in_name = "in_{}".format(i) + decoder_pin = self.pre4x16_inst[pre_num].get_pin(in_name) + + decoder_offset = decoder_pin.center() + input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1) + + self.route_input_bus(decoder_offset, input_offset) + def route_input_bus(self, input_offset, output_offset): """ Route a vertical M2 coordinate to another @@ -267,6 +306,9 @@ class hierarchical_decoder(design.design): for i in range(self.no_of_pre3x8): self.create_pre3x8(i) + for i in range(self.no_of_pre4x16): + self.create_pre4x16(i) + def create_pre2x4(self, num): """ Add a 2x4 predecoder to the left of the origin """ @@ -305,6 +347,24 @@ class hierarchical_decoder(design.design): mod=self.pre3_8)) self.connect_inst(pins) + def create_pre4x16(self, num): + """ Add 4x16 predecoder to the left of the origin and above any 3x8 decoders """ + # If we had 2x4 predecodes, those are used as the lower + # decode output bits + in_index_offset = num * 4 + self.no_of_pre3x8 * 3 + self.no_of_pre2x4 * 2 + out_index_offset = num * 16 + self.no_of_pre3x8 * 8 + self.no_of_pre2x4 * 4 + + pins = [] + for input_index in range(4): + pins.append("addr_{0}".format(input_index + in_index_offset)) + for output_index in range(16): + pins.append("out_{0}".format(output_index + out_index_offset)) + pins.extend(["vdd", "gnd"]) + + self.pre4x16_inst.append(self.add_inst(name="pre4x16_{0}".format(num), + mod=self.pre4_16)) + self.connect_inst(pins) + def place_pre_decoder(self): """ Creates pre-decoder and places labels input address [A] """ @@ -314,11 +374,16 @@ class hierarchical_decoder(design.design): for i in range(self.no_of_pre3x8): self.place_pre3x8(i) + for i in range(self.no_of_pre4x16): + self.place_pre4x16(i) + self.predecode_height = 0 if self.no_of_pre2x4 > 0: self.predecode_height = self.pre2x4_inst[-1].uy() if self.no_of_pre3x8 > 0: self.predecode_height = self.pre3x8_inst[-1].uy() + if self.no_of_pre4x16 > 0: + self.predecode_height = self.pre4x16_inst[-1].uy() def place_pre2x4(self, num): """ Place 2x4 predecoder to the left of the origin """ @@ -333,6 +398,14 @@ class hierarchical_decoder(design.design): offset = vector(-self.pre3_8.width, height) self.pre3x8_inst[num].place(offset) + def place_pre4x16(self, num): + """ Place 3x8 predecoder to the left of the origin and above any 2x4 decoders """ + height = self.no_of_pre2x4 * (self.pre2_4.height + self.predecoder_spacing) \ + + self.no_of_pre3x8 * (self.pre3_8.height + self.predecoder_spacing) \ + + num * (self.pre4_16.height + self.predecoder_spacing) + offset = vector(-self.pre4_16.width, height) + self.pre4x16_inst[num].place(offset) + def create_row_decoder(self): """ Create the row-decoder by placing AND2/AND3 and Inverters and add the primary decoder output pins. """ @@ -468,7 +541,17 @@ class hierarchical_decoder(design.design): x_offset = self.pre3x8_inst[pre_num].rx() + self.output_layer_pitch y_offset = self.pre3x8_inst[pre_num].by() + i * self.cell_height self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset) - + + # FIXME: convert to connect_bus + for pre_num in range(self.no_of_pre4x16): + for i in range(16): + predecode_name = "predecode_{}".format(pre_num * 16 + i + self.no_of_pre3x8 * 8 + self.no_of_pre2x4 * 4) + out_name = "out_{}".format(i) + pin = self.pre4x16_inst[pre_num].get_pin(out_name) + x_offset = self.pre4x16_inst[pre_num].rx() + self.output_layer_pitch + y_offset = self.pre4x16_inst[pre_num].by() + i * self.cell_height + self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset) + def route_bus_to_decoder(self): """ Use the self.predec_groups to determine the connections to the decoder AND gates. @@ -559,7 +642,7 @@ class hierarchical_decoder(design.design): start_layer=supply_pin.layer) # Copy the pins from the predecoders - for pre in self.pre2x4_inst + self.pre3x8_inst: + for pre in self.pre2x4_inst + self.pre3x8_inst + self.pre4x16_inst: for pin_name in ["vdd", "gnd"]: self.copy_layout_pin(pre, pin_name) diff --git a/compiler/pgates/pnand4.py b/compiler/pgates/pnand4.py index ba909dce..5a812799 100644 --- a/compiler/pgates/pnand4.py +++ b/compiler/pgates/pnand4.py @@ -133,7 +133,6 @@ class pnand4(pgate.pgate): # This is the extra space needed to ensure DRC rules # to the active contacts nmos = factory.create(module_type="ptx", tx_type="nmos") - extra_contact_space = max(-nmos.get_pin("D").by(), 0) def create_ptx(self): """ @@ -248,29 +247,29 @@ class pnand4(pgate.pgate): "A", position="left") - self.inputB_yoffset = self.inputA_yoffset + self.m3_pitch + self.inputB_yoffset = self.inputA_yoffset + self.m1_pitch bpin = self.route_input_gate(self.pmos2_inst, self.nmos2_inst, self.inputB_yoffset, "B", position="center") - self.inputC_yoffset = self.inputB_yoffset + self.m3_pitch + self.inputC_yoffset = self.inputB_yoffset + self.m1_pitch cpin = self.route_input_gate(self.pmos3_inst, self.nmos3_inst, self.inputC_yoffset, "C", position="right") - self.inputD_yoffset = self.inputC_yoffset + self.m3_pitch - cpin = self.route_input_gate(self.pmos4_inst, + self.inputD_yoffset = self.inputC_yoffset + self.m1_pitch + dpin = self.route_input_gate(self.pmos4_inst, self.nmos4_inst, self.inputD_yoffset, "D", position="right") if OPTS.tech_name == "sky130": - self.add_enclosure([apin, bpin, cpin], "npc", drc("npc_enclose_poly")) + self.add_enclosure([apin, bpin, cpin, dpin], "npc", drc("npc_enclose_poly")) def route_output(self): """ Route the Z output """ diff --git a/compiler/tests/04_pnand4_test.py b/compiler/tests/04_pnand4_test.py index d2c64a5b..8eb1b4ba 100755 --- a/compiler/tests/04_pnand4_test.py +++ b/compiler/tests/04_pnand4_test.py @@ -26,11 +26,6 @@ class pnand4_test(openram_test): tx = factory.create(module_type="pnand4", size=1) self.local_check(tx) - # debug.info(2, "Checking 3-input nand gate") - # tx = factory.create(module_type="pnand3", size=1, add_wells=False) - # # Only DRC because well contacts will fail LVS - # self.local_drc_check(tx) - globals.end_openram() diff --git a/compiler/tests/06_hierarchical_decoder_test.py b/compiler/tests/06_hierarchical_decoder_test.py index e2b34cba..42bc38cd 100755 --- a/compiler/tests/06_hierarchical_decoder_test.py +++ b/compiler/tests/06_hierarchical_decoder_test.py @@ -8,19 +8,26 @@ # import unittest from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS from sram_factory import factory import debug + class hierarchical_decoder_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) + # Use the 2 port cell since it is usually bigger/easier + OPTS.bitcell = "bitcell_1rw_1r" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + # Checks 2x4 and 2-input NAND decoder debug.info(1, "Testing 16 row sample for hierarchical_decoder") a = factory.create(module_type="hierarchical_decoder", num_outputs=16) @@ -50,6 +57,11 @@ class hierarchical_decoder_test(openram_test): debug.info(1, "Testing 512 row sample for hierarchical_decoder") a = factory.create(module_type="hierarchical_decoder", num_outputs=512) self.local_check(a) + + # Checks 3 x 4x16 and 4-input NAND decoder + debug.info(1, "Testing 4096 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=4096) + self.local_check(a) globals.end_openram()