From 6ac5adaecad77d25d64f95de58c0acfe7ff9a5be Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Wed, 14 Nov 2018 11:41:09 -0800 Subject: [PATCH 01/29] Separate multiport replica bitline from regular replica bitline test --- .../14_replica_bitline_multiport_test.py | 60 +++++++++++++++++++ compiler/tests/14_replica_bitline_test.py | 57 +----------------- 2 files changed, 61 insertions(+), 56 deletions(-) create mode 100755 compiler/tests/14_replica_bitline_multiport_test.py diff --git a/compiler/tests/14_replica_bitline_multiport_test.py b/compiler/tests/14_replica_bitline_multiport_test.py new file mode 100755 index 00000000..55e3e8f0 --- /dev/null +++ b/compiler/tests/14_replica_bitline_multiport_test.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +""" +Run a test on a multiport replica bitline +""" + +import unittest +from testutils import header,openram_test +import sys,os +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +from globals import OPTS +import debug + +class replica_bitline_multiport_test(openram_test): + + def runTest(self): + globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + import replica_bitline + + stages=4 + fanout=4 + rows=13 + + OPTS.bitcell = "bitcell_1rw_1r" + OPTS.replica_bitcell = "replica_bitcell_1rw_1r" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + + debug.info(2, "Testing 1rw 1r RBL with {0} FO4 stages, {1} rows".format(stages,rows)) + a = replica_bitline.replica_bitline(stages,fanout,rows) + self.local_check(a) + + # check replica bitline in pbitcell multi-port + OPTS.bitcell = "pbitcell" + OPTS.replica_bitcell = "replica_pbitcell" + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 0 + OPTS.num_r_ports = 0 + + debug.info(2, "Testing RBL pbitcell 1rw with {0} FO4 stages, {1} rows".format(stages,rows)) + a = replica_bitline.replica_bitline(stages,fanout,rows) + self.local_check(a) + + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 1 + OPTS.num_r_ports = 1 + + debug.info(2, "Testing RBL pbitcell 1rw 1w 1r with {0} FO4 stages, {1} rows".format(stages,rows)) + a = replica_bitline.replica_bitline(stages,fanout,rows) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main() diff --git a/compiler/tests/14_replica_bitline_test.py b/compiler/tests/14_replica_bitline_test.py index 08d9be5f..9efd3eec 100755 --- a/compiler/tests/14_replica_bitline_test.py +++ b/compiler/tests/14_replica_bitline_test.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -Run a test on a delay chain +Run a test on a replica bitline """ import unittest @@ -32,61 +32,6 @@ class replica_bitline_test(openram_test): a = replica_bitline.replica_bitline(stages,fanout,rows) self.local_check(a) - #check replica bitline in handmade multi-port 1rw+1r cell - OPTS.bitcell = "bitcell_1rw_1r" - OPTS.replica_bitcell = "replica_bitcell_1rw_1r" - OPTS.num_rw_ports = 1 - OPTS.num_w_ports = 0 - OPTS.num_r_ports = 1 - stages=4 - fanout=4 - rows=13 - debug.info(2, "Testing RBL with {0} FO4 stages, {1} rows".format(stages,rows)) - a = replica_bitline.replica_bitline(stages,fanout,rows) - self.local_check(a) - - stages=8 - rows=100 - debug.info(2, "Testing RBL with {0} FO4 stages, {1} rows".format(stages,rows)) - a = replica_bitline.replica_bitline(stages,fanout,rows) - self.local_check(a) - - # check replica bitline in pbitcell multi-port - OPTS.bitcell = "pbitcell" - OPTS.replica_bitcell = "replica_pbitcell" - OPTS.num_rw_ports = 1 - OPTS.num_w_ports = 0 - OPTS.num_r_ports = 0 - - stages=4 - fanout=4 - rows=13 - debug.info(2, "Testing RBL with {0} FO4 stages, {1} rows".format(stages,rows)) - a = replica_bitline.replica_bitline(stages,fanout,rows) - self.local_check(a) - - stages=8 - rows=100 - debug.info(2, "Testing RBL with {0} FO4 stages, {1} rows".format(stages,rows)) - a = replica_bitline.replica_bitline(stages,fanout,rows) - self.local_check(a) - - OPTS.num_rw_ports = 1 - OPTS.num_w_ports = 1 - OPTS.num_r_ports = 1 - - stages=4 - fanout=4 - rows=13 - debug.info(2, "Testing RBL with {0} FO4 stages, {1} rows".format(stages,rows)) - a = replica_bitline.replica_bitline(stages,fanout,rows) - self.local_check(a) - - stages=8 - rows=100 - debug.info(2, "Testing RBL with {0} FO4 stages, {1} rows".format(stages,rows)) - a = replica_bitline.replica_bitline(stages,fanout,rows) - self.local_check(a) globals.end_openram() From 3221d3e74411c4eb16c2260d5cf6b0dc2e2ad90c Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Wed, 14 Nov 2018 17:05:23 -0800 Subject: [PATCH 02/29] Add initial support and unit tests for 2 port SRAM --- compiler/modules/bank.py | 3 +- compiler/sram_1bank.py | 114 +++++++++++------- compiler/sram_base.py | 15 ++- .../tests/20_sram_1bank_2mux_1rw_1r_test.py | 43 +++++++ .../tests/20_sram_1bank_nomux_1rw_1r_test.py | 43 +++++++ 5 files changed, 171 insertions(+), 47 deletions(-) create mode 100755 compiler/tests/20_sram_1bank_2mux_1rw_1r_test.py create mode 100755 compiler/tests/20_sram_1bank_nomux_1rw_1r_test.py diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index f2226797..54bb57c4 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -61,7 +61,8 @@ class bank(design.design): #self.add_lvs_correspondence_points() # Remember the bank center for further placement - self.bank_center=self.offset_all_coordinates().scale(-1,-1) + self.bank_array_ll = self.offset_all_coordinates().scale(-1,-1) + self.bank_array_ur = self.bitcell_array_inst.ur() self.DRC_LVS() diff --git a/compiler/sram_1bank.py b/compiler/sram_1bank.py index cfbf1a9e..92c999dc 100644 --- a/compiler/sram_1bank.py +++ b/compiler/sram_1bank.py @@ -43,10 +43,10 @@ class sram_1bank(sram_base): self.data_dff_insts = self.create_data_dff() - def place_modules(self): + def place_instances(self): """ - This places the modules for a single bank SRAM with control - logic. + This places the instances for a single bank SRAM with control + logic and up to 2 ports. """ # No orientation or offset @@ -57,39 +57,70 @@ class sram_1bank(sram_base): # the sense amps/column mux and cell array) # The x-coordinate is placed to allow a single clock wire (plus an extra pitch) # up to the row address DFFs. - for port in self.all_ports: - control_pos = vector(-self.control_logic_rw.width - 2*self.m2_pitch, - self.bank.bank_center.y - self.control_logic_rw.control_logic_center.y) - self.control_logic_insts[port].place(control_pos) - - # The row address bits are placed above the control logic aligned on the right. - row_addr_pos = vector(self.control_logic_insts[0].rx() - self.row_addr_dff.width, - self.control_logic_insts[0].uy()) - self.row_addr_dff_insts[port].place(row_addr_pos) - - # This is M2 pitch even though it is on M1 to help stem via spacings on the trunk - data_gap = -self.m2_pitch*(self.word_size+1) - - # Add the column address below the bank under the control - # The column address flops are aligned with the data flops - if self.col_addr_dff: - col_addr_pos = vector(self.bank.bank_center.x - self.col_addr_dff.width - self.bank.central_bus_width, - data_gap - self.col_addr_dff.height) - self.col_addr_dff_insts[port].place(col_addr_pos) - - # Add the data flops below the bank to the right of the center of bank: - # This relies on the center point of the bank: - # decoder in upper left, bank in upper right, sensing in lower right. - # These flops go below the sensing and leave a gap to channel route to the - # sense amps. - data_pos = vector(self.bank.bank_center.x, - data_gap - self.data_dff.height) - self.data_dff_insts[port].place(data_pos) - - # two supply rails are already included in the bank, so just 2 here. - # self.width = self.bank.width + self.control_logic.width + 2*self.supply_rail_pitch - # self.height = self.bank.height + control_pos = [None]*len(self.all_ports) + row_addr_pos = [None]*len(self.all_ports) + col_addr_pos = [None]*len(self.all_ports) + data_pos = [None]*len(self.all_ports) + # This is M2 pitch even though it is on M1 to help stem via spacings on the trunk + data_gap = self.m2_pitch*(self.word_size+1) + + # Port 0 + port = 0 + control_pos[port] = vector(-self.control_logic_insts[port].width - 2*self.m2_pitch, + self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y) + self.control_logic_insts[port].place(control_pos[port]) + + # The row address bits are placed above the control logic aligned on the right. + row_addr_pos[port] = vector(self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width, + self.control_logic_insts[port].uy()) + self.row_addr_dff_insts[port].place(row_addr_pos[port]) + + # Add the col address flops below the bank to the left of the lower-left of bank array + if self.col_addr_dff: + col_addr_pos[port] = vector(self.bank.bank_array_ll.x - self.col_addr_dff_insts[port].width - self.bank.central_bus_width, + -data_gap - self.col_addr_dff_insts[port].height) + self.col_addr_dff_insts[port].place(col_addr_pos[port]) + + # Add the data flops below the bank to the right of the lower-left of bank array + # This relies on the lower-left of the array of the bank + # decoder in upper left, bank in upper right, sensing in lower right. + # These flops go below the sensing and leave a gap to channel route to the + # sense amps. + if port in self.write_ports: + data_pos[port] = vector(self.bank.bank_array_ll.x, + -data_gap - self.data_dff_insts[port].height) + self.data_dff_insts[port].place(data_pos[port]) + + + # Port 1 + port = 1 + control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2*self.m2_pitch, + self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y) + self.control_logic_insts[port].place(control_pos[port], mirror="MY") + + # The row address bits are placed above the control logic aligned on the left. + row_addr_pos[port] = vector(self.bank_inst.rx() + self.row_addr_dff_insts[port].width, + self.control_logic_insts[port].uy()) + self.row_addr_dff_insts[port].place(row_addr_pos[port], mirror="MY") + + # Add the col address flops above the bank to the right of the upper-right of bank array + if self.col_addr_dff: + col_addr_pos[port] = vector(self.bank.bank_array_ur.x + self.bank.central_bus_width, + self.bank_inst.uy() + data_gap + self.col_addr_dff_insts[port].height) + self.col_addr_dff_insts[port].place(col_addr_pos[port], mirror="MX") + + # Add the data flops above the bank to the left of the upper-right of bank array + # This relies on the upper-right of the array of the bank + # decoder in upper left, bank in upper right, sensing in lower right. + # These flops go below the sensing and leave a gap to channel route to the + # sense amps. + if port in self.write_ports: + data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width, + self.bank.uy() + data_gap + self.data_dff_insts[port].height) + self.data_dff_insts[port].place(data_pos[port], mirror="MX") + + def add_layout_pins(self): """ Add the top-level pins for a single bank SRAM with control. @@ -114,7 +145,7 @@ class sram_1bank(sram_base): for bit in range(self.word_size): self.copy_layout_pin(self.data_dff_insts[port], "din_{}".format(bit), "DIN{0}[{1}]".format(port,bit)) - def route(self): + def route_layout(self): """ Route a single bank SRAM """ self.add_layout_pins() @@ -151,11 +182,12 @@ class sram_1bank(sram_base): dff_clk_pos = dff_clk_pin.center() mid_pos = vector(bank_clk_buf_pos.x, dff_clk_pos.y) self.add_wire(("metal3","via2","metal2"),[dff_clk_pos, mid_pos, bank_clk_buf_pos]) - - data_dff_clk_pin = self.data_dff_insts[port].get_pin("clk") - data_dff_clk_pos = data_dff_clk_pin.center() - mid_pos = vector(bank_clk_buf_pos.x, data_dff_clk_pos.y) - self.add_wire(("metal3","via2","metal2"),[data_dff_clk_pos, mid_pos, bank_clk_buf_pos]) + + if port in self.write_ports: + data_dff_clk_pin = self.data_dff_insts[port].get_pin("clk") + data_dff_clk_pos = data_dff_clk_pin.center() + mid_pos = vector(bank_clk_buf_pos.x, data_dff_clk_pos.y) + self.add_wire(("metal3","via2","metal2"),[data_dff_clk_pos, mid_pos, bank_clk_buf_pos]) # This uses a metal2 track to the right of the control/row addr DFF # to route vertically. diff --git a/compiler/sram_base.py b/compiler/sram_base.py index 74220e63..879c4a04 100644 --- a/compiler/sram_base.py +++ b/compiler/sram_base.py @@ -75,8 +75,9 @@ class sram_base(design): def create_layout(self): """ Layout creation """ - self.place_modules() - self.route() + self.place_instances() + + self.route_layout() self.add_lvs_correspondence_points() @@ -369,9 +370,13 @@ class sram_base(design): def create_data_dff(self): """ Add and place all data flops """ insts = [] - for port in self.write_ports: - insts.append(self.add_inst(name="data_dff{}".format(port), - mod=self.data_dff)) + for port in self.all_ports: + if port in self.write_ports: + insts.append(self.add_inst(name="data_dff{}".format(port), + mod=self.data_dff)) + else: + insts.append(None) + continue # inputs, outputs/output/bar inputs = [] diff --git a/compiler/tests/20_sram_1bank_2mux_1rw_1r_test.py b/compiler/tests/20_sram_1bank_2mux_1rw_1r_test.py new file mode 100755 index 00000000..49fd47be --- /dev/null +++ b/compiler/tests/20_sram_1bank_2mux_1rw_1r_test.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +""" +Run a regression test on a 1 bank, 2 port SRAM +""" + +import unittest +from testutils import header,openram_test +import sys,os +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +from globals import OPTS +import debug + +class sram_1bank_2mux_1rw_1r_test(openram_test): + + def runTest(self): + globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + from sram import sram + from sram_config import sram_config + + OPTS.bitcell = "bitcell_1rw_1r" + OPTS.replica_bitcell = "replica_bitcell_1rw_1r" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + + c = sram_config(word_size=4, + num_words=32, + num_banks=1) + + c.words_per_row=2 + debug.info(1, "Single bank, two way column mux 1rw, 1r with control logic") + a = sram(c, "sram") + self.local_check(a, final_verification=True) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main() diff --git a/compiler/tests/20_sram_1bank_nomux_1rw_1r_test.py b/compiler/tests/20_sram_1bank_nomux_1rw_1r_test.py new file mode 100755 index 00000000..673dcbca --- /dev/null +++ b/compiler/tests/20_sram_1bank_nomux_1rw_1r_test.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +""" +Run a regression test on a 1 bank, 2 port SRAM +""" + +import unittest +from testutils import header,openram_test +import sys,os +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +from globals import OPTS +import debug + +class sram_1bank_nomux_1rw_1r_test(openram_test): + + def runTest(self): + globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + from sram import sram + from sram_config import sram_config + + OPTS.bitcell = "bitcell_1rw_1r" + OPTS.replica_bitcell = "replica_bitcell_1rw_1r" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + + c = sram_config(word_size=4, + num_words=16, + num_banks=1) + + c.words_per_row=1 + debug.info(1, "Single bank, no column mux 1rw, 1r with control logic") + a = sram(c, "sram") + self.local_check(a, final_verification=True) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main() From 3cfefa784f36d26256b8e8a40cecc5a6001a5f5c Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Wed, 14 Nov 2018 17:06:12 -0800 Subject: [PATCH 03/29] Fix run-time bug in combine adjacent pins for supply router --- compiler/router/pin_group.py | 10 +++ compiler/router/router.py | 120 ++++++++++++++++--------------- compiler/router/supply_router.py | 13 +++- 3 files changed, 85 insertions(+), 58 deletions(-) diff --git a/compiler/router/pin_group.py b/compiler/router/pin_group.py index d71c7073..695a5432 100644 --- a/compiler/router/pin_group.py +++ b/compiler/router/pin_group.py @@ -495,6 +495,16 @@ class pin_group: self.grids = pg1.grids | pg2.grids # OR the set of grid locations self.secondary_grids = pg1.secondary_grids | pg2.secondary_grids + def add_group(self, pg): + """ + Combine the pin group into this one. This will add to the first item in the pins + so this should be used before there are disconnected pins. + """ + debug.check(len(self.pins)==1,"Don't know which group to add pins to.") + self.pins[0].update(*pg.pins) # Join the two lists of pins + self.grids |= pg.grids # OR the set of grid locations + self.secondary_grids |= pg.secondary_grids + def add_enclosure(self, cell): """ Add the enclosure shape to the given cell. diff --git a/compiler/router/router.py b/compiler/router/router.py index 18d88df7..727d7753 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -9,9 +9,10 @@ from pin_layout import pin_layout from pin_group import pin_group from vector import vector from vector3d import vector3d -from globals import OPTS +from globals import OPTS,print_time from pprint import pformat import grid_utils +from datetime import datetime class router(router_tech): """ @@ -31,16 +32,18 @@ class router(router_tech): # If didn't specify a gds blockage file, write it out to read the gds # This isn't efficient, but easy for now + #start_time = datetime.now() if not gds_filename: gds_filename = OPTS.openram_temp+"temp.gds" self.cell.gds_write(gds_filename) - + # Load the gds file and read in all the shapes self.layout = gdsMill.VlsiLayout(units=GDS["unit"]) self.reader = gdsMill.Gds2reader(self.layout) self.reader.loadFromFile(gds_filename) self.top_name = self.layout.rootStructureName - + #print_time("GDS read",datetime.now(), start_time) + ### The pin data structures # A map of pin names to a set of pin_layout structures self.pins = {} @@ -127,8 +130,12 @@ class router(router_tech): Pin can either be a label or a location,layer pair: [[x,y],layer]. """ debug.info(1,"Finding pins for {}.".format(pin_name)) + #start_time = datetime.now() self.retrieve_pins(pin_name) + #print_time("Retrieved pins",datetime.now(), start_time) + #start_time = datetime.now() self.analyze_pins(pin_name) + #print_time("Analyzed pins",datetime.now(), start_time) def find_blockages(self): """ @@ -152,97 +159,98 @@ class router(router_tech): self.find_pins(pin) # This will get all shapes as blockages and convert to grid units - # This ignores shapes that were pins + # This ignores shapes that were pins + #start_time = datetime.now() self.find_blockages() + #print_time("Find blockags",datetime.now(), start_time) # Convert the blockages to grid units + #start_time = datetime.now() self.convert_blockages() + #print_time("Find blockags",datetime.now(), start_time) # This will convert the pins to grid units # It must be done after blockages to ensure no DRCs between expanded pins and blocked grids + #start_time = datetime.now() for pin in pin_list: self.convert_pins(pin) - + #print_time("Convert pins",datetime.now(), start_time) + + #start_time = datetime.now() for pin in pin_list: self.combine_adjacent_pins(pin) + #print_time("Combine pins",datetime.now(), start_time) #self.write_debug_gds("debug_combine_pins.gds",stop_program=True) # Separate any adjacent grids of differing net names to prevent wide metal DRC violations # Must be done before enclosing pins + #start_time = datetime.now() self.separate_adjacent_pins(self.supply_rail_space_width) + #print_time("Separate pins",datetime.now(), start_time) # For debug #self.separate_adjacent_pins(1) # Enclose the continguous grid units in a metal rectangle to fix some DRCs + #start_time = datetime.now() self.enclose_pins() + #print_time("Enclose pins",datetime.now(), start_time) #self.write_debug_gds("debug_enclose_pins.gds",stop_program=True) - def combine_adjacent_pins_pass(self, pin_name): + def combine_adjacent_pins(self, pin_name): """ Find pins that have adjacent routing tracks and merge them into a single pin_group. The pins themselves may not be touching, but enclose_pis in the next step will ensure they are touching. """ - - # Make a copy since we are going to add to (and then reduce) this list - pin_groups = self.pin_groups[pin_name].copy() - - # Start as None to signal the first iteration - remove_indices = set() - + debug.info(1,"Combining adjacent pins for {}.".format(pin_name)) + # Find all adjacencies + adjacent_pins = {} for index1,pg1 in enumerate(self.pin_groups[pin_name]): - # Cannot combine more than once - if index1 in remove_indices: - continue for index2,pg2 in enumerate(self.pin_groups[pin_name]): - # Cannot combine with yourself - if index1==index2: + # Cannot combine with yourself, also don't repeat + if index1<=index2: continue - # Cannot combine more than once - if index2 in remove_indices: - continue - # Combine if at least 1 grid cell is adjacent if pg1.adjacent(pg2): - combined = pin_group(pin_name, [], self) - combined.combine_groups(pg1, pg2) - debug.info(3,"Combining {0} {1} {2}:".format(pin_name, index1, index2)) - debug.info(3, " {0}\n {1}".format(pg1.pins, pg2.pins)) - debug.info(3," --> {0}\n {1}".format(combined.pins,combined.grids)) - remove_indices.update([index1,index2]) - pin_groups.append(combined) - break + if not index1 in adjacent_pins.keys(): + adjacent_pins[index1] = set([index2]) + else: + adjacent_pins[index1].add(index2) - # Remove them in decreasing order to not invalidate the indices - debug.info(4,"Removing {}".format(sorted(remove_indices))) - for i in sorted(remove_indices, reverse=True): - del pin_groups[i] - - # Use the new pin group! - self.pin_groups[pin_name] = pin_groups + # Make a list of indices to ensure every group gets in the new set + all_indices = set([x for x in range(len(self.pin_groups[pin_name]))]) - removed_pairs = int(len(remove_indices)/2) - debug.info(1, "Combined {0} pin pairs for {1}".format(removed_pairs,pin_name)) + # Now reconstruct the new groups + new_pin_groups = [] + for index1,index2_set in adjacent_pins.items(): + # Remove the indices if they are added to the new set + all_indices.discard(index1) + all_indices.difference_update(index2_set) + + # Create the combined group starting with the first item + combined = self.pin_groups[pin_name][index1] + # Add all of the other items that overlapped + for index2 in index2_set: + pg = self.pin_groups[pin_name][index2] + combined.add_group(pg) + debug.info(3,"Combining {0} {1}:".format(pin_name, index2)) + debug.info(3, " {0}\n {1}".format(combined.pins, pg.pins)) + debug.info(3," --> {0}\n {1}".format(combined.pins,combined.grids)) + new_pin_groups.append(combined) + + # Add the pin groups that weren't added to the new set + for index in all_indices: + new_pin_groups.append(self.pin_groups[pin_name][index]) + + old_size = len(self.pin_groups[pin_name]) + # Use the new pin group! + self.pin_groups[pin_name] = new_pin_groups + removed_pairs = old_size - len(new_pin_groups) + debug.info(1, "Combined {0} pin groups for {1}".format(removed_pairs,pin_name)) return removed_pairs - def combine_adjacent_pins(self, pin_name): - """ - Make multiple passes of the combine adjacent pins until we have no - more combinations or hit an iteration limit. - """ - debug.info(1,"Combining adjacent pins for {}.".format(pin_name)) - # Start as None to signal the first iteration - num_removed_pairs = None - - # Just used in case there's a circular combination or something weird - for iteration_count in range(10): - num_removed_pairs = self.combine_adjacent_pins_pass(pin_name) - if num_removed_pairs==0: - break - else: - debug.warning("Did not converge combining adjacent pins in supply router.") def separate_adjacent_pins(self, separation): """ @@ -271,7 +279,7 @@ class router(router_tech): debug.info(1,"Comparing {0} and {1} adjacency".format(pin_name1, pin_name2)) for index1,pg1 in enumerate(self.pin_groups[pin_name1]): for index2,pg2 in enumerate(self.pin_groups[pin_name2]): - # FIXME: Use separation distance and edge grids only + # FIgXME: Use separation distance and edge grids only grids_g1, grids_g2 = pg1.adjacent_grids(pg2, separation) # These should have the same length, so... if len(grids_g1)>0: diff --git a/compiler/router/supply_router.py b/compiler/router/supply_router.py index 00ea30bc..a56b1a42 100644 --- a/compiler/router/supply_router.py +++ b/compiler/router/supply_router.py @@ -2,13 +2,14 @@ import gdsMill import tech import math import debug -from globals import OPTS +from globals import OPTS,print_time from contact import contact from pin_group import pin_group from pin_layout import pin_layout from vector3d import vector3d from router import router from direction import direction +from datetime import datetime import grid import grid_utils @@ -68,10 +69,13 @@ class supply_router(router): self.compute_supply_rail_dimensions() # Get the pin shapes + #start_time = datetime.now() self.find_pins_and_blockages([self.vdd_name, self.gnd_name]) + #print_time("Pins and blockages",datetime.now(), start_time) #self.write_debug_gds("pin_enclosures.gds",stop_program=True) # Add the supply rails in a mesh network and connect H/V with vias + #start_time = datetime.now() # Block everything self.prepare_blockages(self.gnd_name) # Determine the rail locations @@ -82,15 +86,20 @@ class supply_router(router): # Determine the rail locations self.route_supply_rails(self.vdd_name,1) #self.write_debug_gds("debug_rails.gds",stop_program=True) - + #print_time("Supply rails",datetime.now(), start_time) + + #start_time = datetime.now() self.route_simple_overlaps(vdd_name) self.route_simple_overlaps(gnd_name) + #print_time("Simple overlaps",datetime.now(), start_time) #self.write_debug_gds("debug_simple_route.gds",stop_program=False) # Route the supply pins to the supply rails # Route vdd first since we want it to be shorter + #start_time = datetime.now() self.route_pins_to_rails(vdd_name) self.route_pins_to_rails(gnd_name) + #print_time("Routing",datetime.now(), start_time) #self.write_debug_gds("debug_pin_routes.gds",stop_program=True) #self.write_debug_gds("final.gds",False) From 66982a928368f488f2f91bf6f205c16772fe0377 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Wed, 14 Nov 2018 17:11:23 -0800 Subject: [PATCH 04/29] Only add second port if it is specified. --- compiler/sram_1bank.py | 47 +++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/compiler/sram_1bank.py b/compiler/sram_1bank.py index 92c999dc..1176ff07 100644 --- a/compiler/sram_1bank.py +++ b/compiler/sram_1bank.py @@ -93,32 +93,33 @@ class sram_1bank(sram_base): self.data_dff_insts[port].place(data_pos[port]) - # Port 1 - port = 1 - control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2*self.m2_pitch, - self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y) - self.control_logic_insts[port].place(control_pos[port], mirror="MY") + if len(self.all_ports)>1: + # Port 1 + port = 1 + control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2*self.m2_pitch, + self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y) + self.control_logic_insts[port].place(control_pos[port], mirror="MY") - # The row address bits are placed above the control logic aligned on the left. - row_addr_pos[port] = vector(self.bank_inst.rx() + self.row_addr_dff_insts[port].width, - self.control_logic_insts[port].uy()) - self.row_addr_dff_insts[port].place(row_addr_pos[port], mirror="MY") + # The row address bits are placed above the control logic aligned on the left. + row_addr_pos[port] = vector(self.bank_inst.rx() + self.row_addr_dff_insts[port].width, + self.control_logic_insts[port].uy()) + self.row_addr_dff_insts[port].place(row_addr_pos[port], mirror="MY") - # Add the col address flops above the bank to the right of the upper-right of bank array - if self.col_addr_dff: - col_addr_pos[port] = vector(self.bank.bank_array_ur.x + self.bank.central_bus_width, - self.bank_inst.uy() + data_gap + self.col_addr_dff_insts[port].height) - self.col_addr_dff_insts[port].place(col_addr_pos[port], mirror="MX") + # Add the col address flops above the bank to the right of the upper-right of bank array + if self.col_addr_dff: + col_addr_pos[port] = vector(self.bank.bank_array_ur.x + self.bank.central_bus_width, + self.bank_inst.uy() + data_gap + self.col_addr_dff_insts[port].height) + self.col_addr_dff_insts[port].place(col_addr_pos[port], mirror="MX") - # Add the data flops above the bank to the left of the upper-right of bank array - # This relies on the upper-right of the array of the bank - # decoder in upper left, bank in upper right, sensing in lower right. - # These flops go below the sensing and leave a gap to channel route to the - # sense amps. - if port in self.write_ports: - data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width, - self.bank.uy() + data_gap + self.data_dff_insts[port].height) - self.data_dff_insts[port].place(data_pos[port], mirror="MX") + # Add the data flops above the bank to the left of the upper-right of bank array + # This relies on the upper-right of the array of the bank + # decoder in upper left, bank in upper right, sensing in lower right. + # These flops go below the sensing and leave a gap to channel route to the + # sense amps. + if port in self.write_ports: + data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width, + self.bank.uy() + data_gap + self.data_dff_insts[port].height) + self.data_dff_insts[port].place(data_pos[port], mirror="MX") def add_layout_pins(self): From 21d111acfef582a6d52f6b3a62879b7c1ac32f36 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Thu, 15 Nov 2018 10:30:38 -0800 Subject: [PATCH 05/29] Move wordline driver clock line below decoder. Fix port 1 clock route DRC. --- compiler/modules/bank.py | 4 ++-- compiler/sram_1bank.py | 22 +++++++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 54bb57c4..4f493b46 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -1190,8 +1190,8 @@ class bank(design.design): # clk to wordline_driver control_signal = self.prefix+"clk_buf{}".format(port) - pin_pos = self.wordline_driver_inst[port].get_pin("en").uc() - mid_pos = pin_pos + vector(0,self.m1_pitch) + pin_pos = self.wordline_driver_inst[port].get_pin("en").bc() + mid_pos = pin_pos - vector(0,self.m1_pitch) control_x_offset = self.bus_xoffset[port][control_signal].x control_pos = vector(control_x_offset, mid_pos.y) self.add_wire(("metal1","via1","metal2"),[pin_pos, mid_pos, control_pos]) diff --git a/compiler/sram_1bank.py b/compiler/sram_1bank.py index 1176ff07..e1983ac5 100644 --- a/compiler/sram_1bank.py +++ b/compiler/sram_1bank.py @@ -67,6 +67,7 @@ class sram_1bank(sram_base): # Port 0 port = 0 + # This includes 2 M2 pitches for the row addr clock line control_pos[port] = vector(-self.control_logic_insts[port].width - 2*self.m2_pitch, self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y) self.control_logic_insts[port].place(control_pos[port]) @@ -96,12 +97,13 @@ class sram_1bank(sram_base): if len(self.all_ports)>1: # Port 1 port = 1 + # This includes 2 M2 pitches for the row addr clock line control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2*self.m2_pitch, self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y) self.control_logic_insts[port].place(control_pos[port], mirror="MY") # The row address bits are placed above the control logic aligned on the left. - row_addr_pos[port] = vector(self.bank_inst.rx() + self.row_addr_dff_insts[port].width, + row_addr_pos[port] = vector(control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width, self.control_logic_insts[port].uy()) self.row_addr_dff_insts[port].place(row_addr_pos[port], mirror="MY") @@ -190,14 +192,20 @@ class sram_1bank(sram_base): mid_pos = vector(bank_clk_buf_pos.x, data_dff_clk_pos.y) self.add_wire(("metal3","via2","metal2"),[data_dff_clk_pos, mid_pos, bank_clk_buf_pos]) - # This uses a metal2 track to the right of the control/row addr DFF - # to route vertically. + # This uses a metal2 track to the right (for port0) of the control/row addr DFF + # to route vertically. For port1, it is to the left. control_clk_buf_pin = self.control_logic_insts[port].get_pin("clk_buf") - control_clk_buf_pos = control_clk_buf_pin.rc() row_addr_clk_pin = self.row_addr_dff_insts[port].get_pin("clk") - row_addr_clk_pos = row_addr_clk_pin.rc() - mid1_pos = vector(self.row_addr_dff_insts[port].rx() + self.m2_pitch, - row_addr_clk_pos.y) + if port%2: + control_clk_buf_pos = control_clk_buf_pin.lc() + row_addr_clk_pos = row_addr_clk_pin.lc() + mid1_pos = vector(self.row_addr_dff_insts[port].lx() - self.m2_pitch, + row_addr_clk_pos.y) + else: + control_clk_buf_pos = control_clk_buf_pin.rc() + row_addr_clk_pos = row_addr_clk_pin.rc() + mid1_pos = vector(self.row_addr_dff_insts[port].rx() + self.m2_pitch, + row_addr_clk_pos.y) mid2_pos = vector(mid1_pos.x, control_clk_buf_pos.y) # Note, the via to the control logic is taken care of when we route From 712b71c5ca0095a99fb1dee6d1da8ac60cf8ac63 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Thu, 15 Nov 2018 15:26:59 -0800 Subject: [PATCH 06/29] Mirror port 1 column decoder in X and Y --- compiler/modules/bank.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 4f493b46..936f6804 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -724,7 +724,7 @@ class bank(design.design): for port in self.all_ports: if port%2 == 1: - mirror = "MY" + mirror = "XY" else: mirror = "R0" self.column_decoder_inst[port].place(offset=offsets[port], mirror=mirror) From 65d341619cd1fb40214b50420238ef8c6dfafe87 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Thu, 15 Nov 2018 15:48:15 -0800 Subject: [PATCH 07/29] Fix typos in README --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e6a20328..937a724d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # OpenRAM -[![pipeline status](https://scone.soe.ucsc.edu:8888/mrg/PrivateRAM/badges/dev/pipeline.svg?private_token=ynB6rSFLzvKUseoBPcwV)](https://github.com/VLSIDA/PrivateRAM/commits) +Master: [![pipeline status](https://scone.soe.ucsc.edu:8888/mrg/PrivateRAM/badges/master/pipeline.svg?private_token=ynB6rSFLzvKUseoBPcwV)](https://github.com/VLSIDA/PrivateRAM/commits/master) +Dev: [![pipeline status](https://scone.soe.ucsc.edu:8888/mrg/PrivateRAM/badges/dev/pipeline.svg?private_token=ynB6rSFLzvKUseoBPcwV)](https://github.com/VLSIDA/PrivateRAM/commits/dev) [![Download](images/download.svg)](https://github.com/VLSIDA/PrivateRAM/archive/dev.zip) [![License: BSD 3-clause](./images/license_badge.svg)](./LICENSE) @@ -87,7 +88,7 @@ output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name) You can then run OpenRAM by executing: ``` -python3 $OPENRAM\_HOME/openram.py myconfig +python3 $OPENRAM_HOME/openram.py myconfig ``` You can see all of the options for the configuration file in $OPENRAM\_HOME/options.py @@ -192,9 +193,9 @@ OpenRAM is licensed under the [BSD 3-clause License](./LICENSE). [VLSIARCH]: https://vlsiarch.ecen.okstate.edu/ [OpenRAMpaper]: https://ieeexplore.ieee.org/document/7827670/ -[Github issues]: https://github.com/PrivateRAM/PrivateRAM/issues -[Github pull request]: https://github.com/PrivateRAM/PrivateRAM/pulls -[Github projects]: https://github.com/PrivateRAM/PrivateRAM/projects +[Github issues]: https://github.com/VLSIDA/PrivateRAM/issues +[Github pull request]: https://github.com/VLSIDA/PrivateRAM/pulls +[Github projects]: https://github.com/VLSIDA/PrivateRAM/projects [email me]: mailto:mrg+openram@ucsc.edu [dev-group]: mailto:openram-dev-group@ucsc.edu From 43472dfa468c68e4950f11f4158c0d4163140e59 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Thu, 15 Nov 2018 16:55:18 -0800 Subject: [PATCH 08/29] Modify sense amp to cross coupled inverter --- docs/figs/sense_amp_schem.pdf | Bin 7161 -> 14383 bytes docs/figs/sense_amp_schem.svg | 667 ++++++---------------------------- 2 files changed, 106 insertions(+), 561 deletions(-) diff --git a/docs/figs/sense_amp_schem.pdf b/docs/figs/sense_amp_schem.pdf index 0b062ffe455dc03185f1c0ad6d0951d1649c3484..01c202be11778eec809a20c757cde880310acdab 100644 GIT binary patch literal 14383 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 literal 7161 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 S)96A)#PB - - - - - - - - - - - image/svg+xml - - - - + + + Produced by OmniGraffle 7.6.1 + 2018-11-16 00:52:28 +0000 - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - vdd - DATA - br - bl - en - en - en - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + Canvas 1 + + + Layer 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vdd + + + DA + T + A + + + br + + + bl + + + en + + + en + + + en + + + + + + + + + + + + + + + + + From 68ac7e595505ed0819018ab6e6ed97fd988b16f2 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Thu, 15 Nov 2018 17:27:58 -0800 Subject: [PATCH 09/29] Fix offset of column decoder with new mirroring --- compiler/modules/bank.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 936f6804..3a2d3f54 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -233,8 +233,8 @@ class bank(design.design): self.row_decoder_offsets[port] = vector(-x_offset,0) # LOWER LEFT QUADRANT - # Place the col decoder right aligned with row decoder (x_offset doesn't change) - # Below the bitcell array + # Place the col decoder left aligned with row decoder (x_offset doesn't change) + # Below the bitcell array with well spacing if self.col_addr_size > 0: y_offset = self.column_decoder.height else: @@ -291,8 +291,11 @@ class bank(design.design): # UPPER RIGHT QUADRANT # Place the col decoder right aligned with row decoder (x_offset doesn't change) - # Below the bitcell array - y_offset = self.bitcell_array.height + self.m2_gap + # Above the bitcell array with a well spacing + if self.col_addr_size > 0: + y_offset = self.bitcell_array.height + self.column_decoder.height + else: + y_offset = self.bitcell_array.height y_offset += 2*drc("well_to_well") self.column_decoder_offsets[port] = vector(x_offset,y_offset) From ff67e772fa86e5054127c70b4a486f614546d36f Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Thu, 15 Nov 2018 17:28:06 -0800 Subject: [PATCH 10/29] Fix extra escape in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e6a20328..63a9ac55 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ word_size = 2 # Number of words in the memory num_words = 16 -# Technology to use in $OPENRAM\_TECH +# Technology to use in $OPENRAM_TECH tech_name = "scn4m_subm" # Process corners to characterize process_corners = ["TT"] From 26814f92ef3063299d657b039a4d58d377df63ab Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Fri, 16 Nov 2018 08:25:04 -0800 Subject: [PATCH 11/29] Clarify basic setup instructions. --- README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 38b906b5..4d342362 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # OpenRAM -Master: [![pipeline status](https://scone.soe.ucsc.edu:8888/mrg/PrivateRAM/badges/master/pipeline.svg?private_token=ynB6rSFLzvKUseoBPcwV)](https://github.com/VLSIDA/PrivateRAM/commits/master) -Dev: [![pipeline status](https://scone.soe.ucsc.edu:8888/mrg/PrivateRAM/badges/dev/pipeline.svg?private_token=ynB6rSFLzvKUseoBPcwV)](https://github.com/VLSIDA/PrivateRAM/commits/dev) -[![Download](images/download.svg)](https://github.com/VLSIDA/PrivateRAM/archive/dev.zip) +Stable: [![pipeline status](https://scone.soe.ucsc.edu:8888/mrg/PrivateRAM/badges/master/pipeline.svg?private_token=ynB6rSFLzvKUseoBPcwV)](https://github.com/VLSIDA/PrivateRAM/commits/master) +Unstable: [![pipeline status](https://scone.soe.ucsc.edu:8888/mrg/PrivateRAM/badges/dev/pipeline.svg?private_token=ynB6rSFLzvKUseoBPcwV)](https://github.com/VLSIDA/PrivateRAM/commits/dev) +[![Download](images/download.svg)](https://github.com/VLSIDA/PrivateRAM/archive/master.zip) [![License: BSD 3-clause](./images/license_badge.svg)](./LICENSE) An open-source static random access memory (SRAM) compiler. @@ -39,6 +39,12 @@ For example add this to your .bashrc: export OPENRAM_TECH="$HOME/openram/technology" ``` +You may also wish to add OPENRAM\_HOME to your PYTHONPATH: + +``` + export PYTHONPATH="$PYTHONPATH:$OPENRAM_HOME" +``` + We include the tech files necessary for [FreePDK45] and [SCMOS] SCN4M_SUBM. The [SCMOS] spice models, however, are generic and should be replaced with foundry models. If you are using [FreePDK45], you @@ -56,8 +62,7 @@ We have included the most recent SCN4M_SUBM design rules from [Qflow]. # Basic Usage Once you have defined the environment, you can run OpenRAM from the command line -using a single configuration file written in Python. You may wish to add -$OPENRAM\_HOME to your $PYTHONPATH. +using a single configuration file written in Python. For example, create a file called *myconfig.py* specifying the following parameters for your memory: From ee9aad1b21605d45d795e8df6d1d498bd59bce08 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Fri, 16 Nov 2018 08:26:09 -0800 Subject: [PATCH 12/29] Errors in contributors. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4d342362..03b7213f 100644 --- a/README.md +++ b/README.md @@ -181,7 +181,7 @@ OpenRAM is licensed under the [BSD 3-clause License](./LICENSE). - [Matthew Guthaus] from [VLSIDA] created the OpenRAM project and is the lead architect. - [James Stine] from [VLSIARCH] co-founded the project. - Hunter Nichols maintains and updates the timing characterization. -- Michael Grims created and maintains the multiport netlist code. +- Michael Grimes created and maintains the multiport netlist code. - Jennifer Sowash is creating the OpenRAM IP library. - Jesse Cirimelli-Low created the datasheet generation. - Samira Ataei created early multi-bank layouts and control logic. @@ -190,6 +190,8 @@ OpenRAM is licensed under the [BSD 3-clause License](./LICENSE). - Brian Chen created early prototypes of the timing characterizer. - Jeff Butera created early prototypes of the bank layout. +If I forgot to add you, please let me know! + * * * [Matthew Guthaus]: https://users.soe.ucsc.edu/~mrg From 5e0eb609dace0f695d0d920e1a5177930497f3e1 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Fri, 16 Nov 2018 11:48:41 -0800 Subject: [PATCH 13/29] Check for single top-level structure in vlsiLayout. Don't allow dff_inv and dff_buf to have same names. --- compiler/base/geometry.py | 3 ++- compiler/base/hierarchy_design.py | 15 ++++++++------- compiler/gdsMill/gdsMill/vlsiLayout.py | 3 ++- compiler/modules/dff_buf.py | 6 ++++-- compiler/modules/dff_buf_array.py | 6 ++++-- compiler/modules/dff_inv.py | 6 ++++-- compiler/modules/dff_inv_array.py | 6 ++++-- 7 files changed, 28 insertions(+), 17 deletions(-) diff --git a/compiler/base/geometry.py b/compiler/base/geometry.py index 33bcaaa2..838828df 100644 --- a/compiler/base/geometry.py +++ b/compiler/base/geometry.py @@ -200,18 +200,19 @@ class instance(geometry): self.mod.gds_write_file(self.gds) # now write an instance of my module/structure new_layout.addInstance(self.gds, + self.mod.name, offsetInMicrons=self.offset, mirror=self.mirror, rotate=self.rotate) def place(self, offset, mirror="R0", rotate=0): """ This updates the placement of an instance. """ - debug.info(3, "placing instance {}".format(self.name)) # Update the placement of an already added instance self.offset = vector(offset).snap_to_grid() self.mirror = mirror self.rotate = rotate self.update_boundary() + debug.info(3, "placing instance {}".format(self)) def get_pin(self,name,index=-1): diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index 5948d066..fb6db0f8 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -34,20 +34,21 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): # because each reference must be a unique name. # These modules ensure unique names or have no changes if they # aren't unique - ok_list = ['ms_flop', - 'dff', - 'dff_buf', - 'bitcell', - 'contact', + ok_list = ['contact', 'ptx', 'sram', 'hierarchical_predecode2x4', 'hierarchical_predecode3x8'] - if name not in hierarchy_design.name_map: + # Library cells don't change + if self.is_library_cell: + return + # Name is unique so far + elif name not in hierarchy_design.name_map: hierarchy_design.name_map.append(name) else: + # Name is in our list of exceptions (they don't change) for ok_names in ok_list: - if ok_names in self.__class__.__name__: + if ok_names == self.__class__.__name__: break else: debug.error("Duplicate layout reference name {0} of class {1}. GDS2 requires names be unique.".format(name,self.__class__),-1) diff --git a/compiler/gdsMill/gdsMill/vlsiLayout.py b/compiler/gdsMill/gdsMill/vlsiLayout.py index 92d7d2a2..42921812 100644 --- a/compiler/gdsMill/gdsMill/vlsiLayout.py +++ b/compiler/gdsMill/gdsMill/vlsiLayout.py @@ -148,13 +148,13 @@ class VlsiLayout: structureNames=[] for name in self.structures: structureNames.append(name) - for name in self.structures: if(len(self.structures[name].srefs)>0): #does this structure reference any others? for sref in self.structures[name].srefs: #go through each reference if sref.sName in structureNames: #and compare to our list structureNames.remove(sref.sName) + debug.check(len(structureNames)==1,"Multiple possible root structures in the layout: {}".format(str(structureNames))) self.rootStructureName = structureNames[0] def traverseTheHierarchy(self, startingStructureName=None, delegateFunction = None, @@ -304,6 +304,7 @@ class VlsiLayout: debug.info(1,"DEBUG: Structure %s Found"%StructureName) StructureFound = True + debug.check(StructureFound,"Could not find layout to instantiate {}".format(StructureName)) # If layoutToAdd is a unique object (not this), then copy hierarchy, diff --git a/compiler/modules/dff_buf.py b/compiler/modules/dff_buf.py index 6361b220..48d0dc32 100644 --- a/compiler/modules/dff_buf.py +++ b/compiler/modules/dff_buf.py @@ -12,11 +12,13 @@ class dff_buf(design.design): with two inverters, of variable size, to provide q and qbar. This is to enable driving large fanout loads. """ - + unique_id = 1 + def __init__(self, inv1_size=2, inv2_size=4, name=""): if name=="": - name = "dff_buf_{0}_{1}".format(inv1_size, inv2_size) + name = "dff_buf_{0}".format(dff_buf.unique_id) + dff_buf.unique_id += 1 design.design.__init__(self, name) debug.info(1, "Creating {}".format(self.name)) diff --git a/compiler/modules/dff_buf_array.py b/compiler/modules/dff_buf_array.py index 9223e276..cf2bbef9 100644 --- a/compiler/modules/dff_buf_array.py +++ b/compiler/modules/dff_buf_array.py @@ -11,13 +11,15 @@ class dff_buf_array(design.design): This is a simple row (or multiple rows) of flops. Unlike the data flops, these are never spaced out. """ - + unique_id = 1 + def __init__(self, rows, columns, inv1_size=2, inv2_size=4, name=""): self.rows = rows self.columns = columns if name=="": - name = "dff_buf_array_{0}x{1}".format(rows, columns) + name = "dff_buf_array_{0}x{1}_{2}".format(rows, columns, dff_buf_array.unique_id) + dff_buf_array.unique_id += 1 design.design.__init__(self, name) debug.info(1, "Creating {}".format(self.name)) self.inv1_size = inv1_size diff --git a/compiler/modules/dff_inv.py b/compiler/modules/dff_inv.py index 3a06c9c9..076a37d8 100644 --- a/compiler/modules/dff_inv.py +++ b/compiler/modules/dff_inv.py @@ -11,11 +11,13 @@ class dff_inv(design.design): This is a simple DFF with an inverted output. Some DFFs do not have Qbar, so this will create it. """ - + unique_id = 1 + def __init__(self, inv_size=2, name=""): if name=="": - name = "dff_inv_{0}".format(inv_size) + name = "dff_inv_{0}".format(dff_inv.unique_id) + dff_inv.unique_id += 1 design.design.__init__(self, name) debug.info(1, "Creating {}".format(self.name)) self.inv_size = inv_size diff --git a/compiler/modules/dff_inv_array.py b/compiler/modules/dff_inv_array.py index aafe87e2..4143f3e3 100644 --- a/compiler/modules/dff_inv_array.py +++ b/compiler/modules/dff_inv_array.py @@ -11,13 +11,15 @@ class dff_inv_array(design.design): This is a simple row (or multiple rows) of flops. Unlike the data flops, these are never spaced out. """ - + unique_id = 1 + def __init__(self, rows, columns, inv_size=2, name=""): self.rows = rows self.columns = columns if name=="": - name = "dff_inv_array_{0}x{1}".format(rows, columns) + name = "dff_inv_array_{0}x{1}_{2}".format(rows, columns, dff_inv_array.unique_id) + dff_inv_array.unique_id += 1 design.design.__init__(self, name) debug.info(1, "Creating {}".format(self.name)) self.inv_size = inv_size From e040fd12f90ec1c11cad22a4b44182a825e1e8e9 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Fri, 16 Nov 2018 12:00:23 -0800 Subject: [PATCH 14/29] Bitcell and bitcell array can be named the same. --- compiler/base/hierarchy_design.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index fb6db0f8..7b95fbb1 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -36,6 +36,8 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): # aren't unique ok_list = ['contact', 'ptx', + 'pbitcell', + 'bitcell', 'sram', 'hierarchical_predecode2x4', 'hierarchical_predecode3x8'] From ca750b698a376ed2bc2a5df9be61b6bd47e98479 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Fri, 16 Nov 2018 12:52:22 -0800 Subject: [PATCH 15/29] Uniquify bitcell array --- compiler/base/hierarchy_design.py | 3 ++- compiler/modules/bitcell_array.py | 9 +++++++-- compiler/modules/replica_bitline.py | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index 7b95fbb1..68749693 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -37,10 +37,11 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): ok_list = ['contact', 'ptx', 'pbitcell', - 'bitcell', + 'replica_pbitcell', 'sram', 'hierarchical_predecode2x4', 'hierarchical_predecode3x8'] + # Library cells don't change if self.is_library_cell: return diff --git a/compiler/modules/bitcell_array.py b/compiler/modules/bitcell_array.py index 1d9b1539..d42c134e 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -4,7 +4,7 @@ from tech import drc, spice from vector import vector from globals import OPTS - +unique_id = 1 class bitcell_array(design.design): """ @@ -12,8 +12,13 @@ class bitcell_array(design.design): and word line is connected by abutment. Connects the word lines and bit lines. """ + unique_id = 1 + + def __init__(self, cols, rows, name=""): - def __init__(self, cols, rows, name="bitcell_array"): + if name == "": + name = "bitcell_array_{0}x{1}_{2}".format(rows,cols,bitcell_array.unique_id) + bitcell_array.unique_id += 1 design.design.__init__(self, name) debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols)) diff --git a/compiler/modules/replica_bitline.py b/compiler/modules/replica_bitline.py index 5c14b14d..e349fa89 100644 --- a/compiler/modules/replica_bitline.py +++ b/compiler/modules/replica_bitline.py @@ -90,7 +90,7 @@ class replica_bitline(design.design): 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.bitcell_loads) + self.rbl = bitcell_array(cols=1, rows=self.bitcell_loads) self.add_mod(self.rbl) # FIXME: The FO and depth of this should be tuned From 4997a2051174d5680e1f6919df45c52ac0b26a7b Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Fri, 16 Nov 2018 13:37:17 -0800 Subject: [PATCH 16/29] Must set library cell flag for netlist only mode as well --- compiler/base/hierarchy_layout.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 229bb0f8..07d4f611 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -447,6 +447,11 @@ class layout(lef.lef): def gds_read(self): """Reads a GDSII file in the library and checks if it exists Otherwise, start a new layout for dynamic generation.""" + + # This must be done for netlist only mode too + if os.path.isfile(self.gds_file): + self.is_library_cell=True + if OPTS.netlist_only: self.gds = None return @@ -454,7 +459,6 @@ class layout(lef.lef): # open the gds file if it exists or else create a blank layout if os.path.isfile(self.gds_file): debug.info(3, "opening {}".format(self.gds_file)) - self.is_library_cell=True self.gds = gdsMill.VlsiLayout(units=GDS["unit"]) reader = gdsMill.Gds2reader(self.gds) reader.loadFromFile(self.gds_file) From b13d938ea85e6e43741b0909039e3951e988e1f3 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Fri, 16 Nov 2018 14:10:49 -0800 Subject: [PATCH 17/29] Add m3m4 short hand in design class --- compiler/base/contact.py | 2 +- compiler/base/design.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/base/contact.py b/compiler/base/contact.py index a2758c56..db415179 100644 --- a/compiler/base/contact.py +++ b/compiler/base/contact.py @@ -172,5 +172,5 @@ active = contact(layer_stack=("active", "contact", "poly")) poly = contact(layer_stack=("poly", "contact", "metal1")) m1m2 = contact(layer_stack=("metal1", "via1", "metal2")) m2m3 = contact(layer_stack=("metal2", "via2", "metal3")) -#m3m4 = contact(layer_stack=("metal3", "via3", "metal4")) +m3m4 = contact(layer_stack=("metal3", "via3", "metal4")) diff --git a/compiler/base/design.py b/compiler/base/design.py index 51994275..43957cb6 100644 --- a/compiler/base/design.py +++ b/compiler/base/design.py @@ -22,9 +22,7 @@ class design(hierarchy_design): self.m1_pitch = max(contact.m1m2.width,contact.m1m2.height) + max(self.m1_space, self.m2_space) self.m2_pitch = max(contact.m2m3.width,contact.m2m3.height) + max(self.m2_space, self.m3_space) - # SCMOS doesn't have m4... - #self.m3_pitch = max(contact.m3m4.width,contact.m3m4.height) + max(self.m3_space, self.m4_space) - self.m3_pitch = self.m2_pitch + self.m3_pitch = max(contact.m3m4.width,contact.m3m4.height) + max(self.m3_space, self.m4_space) def setup_drc_constants(self): """ These are some DRC constants used in many places in the compiler.""" @@ -38,6 +36,8 @@ class design(hierarchy_design): self.m2_space = drc("metal2_to_metal2") self.m3_width = drc("minwidth_metal3") self.m3_space = drc("metal3_to_metal3") + self.m4_width = drc("minwidth_metal4") + self.m4_space = drc("metal4_to_metal4") self.active_width = drc("minwidth_active") self.active_space = drc("active_to_body_active") self.contact_width = drc("minwidth_contact") From 8f28f4fde5c4bdce1faab25c863dcd0696560d71 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Fri, 16 Nov 2018 15:03:12 -0800 Subject: [PATCH 18/29] Don't always add all 3 types of contorl. Add write and read only port lists. --- compiler/base/design.py | 6 ++++++ compiler/sram_base.py | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/base/design.py b/compiler/base/design.py index 43957cb6..f52aa100 100644 --- a/compiler/base/design.py +++ b/compiler/base/design.py @@ -65,8 +65,12 @@ class design(hierarchy_design): self.readwrite_ports = [] # These are the read/write and write-only port indices self.write_ports = [] + # These are the write-only port indices. + self.writeonly_ports = [] # These are teh read/write and read-only port indice self.read_ports = [] + # These are the read-only port indices. + self.readonly_ports = [] # These are all the ports self.all_ports = list(range(total_ports)) @@ -78,9 +82,11 @@ class design(hierarchy_design): port_number += 1 for port in range(OPTS.num_w_ports): self.write_ports.append(port_number) + self.writeonly_ports.append(port_number) port_number += 1 for port in range(OPTS.num_r_ports): self.read_ports.append(port_number) + self.readonly_ports.append(port_number) port_number += 1 def analytical_power(self, proc, vdd, temp, load): diff --git a/compiler/sram_base.py b/compiler/sram_base.py index 879c4a04..29c3cbb9 100644 --- a/compiler/sram_base.py +++ b/compiler/sram_base.py @@ -226,12 +226,12 @@ class sram_base(design): words_per_row=self.words_per_row, port_type="rw") self.add_mod(self.control_logic_rw) - if len(self.write_ports)>0: + if len(self.writeonly_ports)>0: self.control_logic_w = self.mod_control_logic(num_rows=self.num_rows, words_per_row=self.words_per_row, port_type="w") self.add_mod(self.control_logic_w) - if len(self.read_ports)>0: + if len(self.readonly_ports)>0: self.control_logic_r = self.mod_control_logic(num_rows=self.num_rows, words_per_row=self.words_per_row, port_type="r") From b89c011e41d9b0a0677017a77f9f3354b792e36e Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Fri, 16 Nov 2018 15:31:22 -0800 Subject: [PATCH 19/29] Add psram 1w/1r test. Fix bl/br port naming errors in bank. --- compiler/modules/bank.py | 61 ++++++++++--------- .../tests/20_psram_1bank_2mux_1w_1r_test.py | 44 +++++++++++++ 2 files changed, 76 insertions(+), 29 deletions(-) create mode 100755 compiler/tests/20_psram_1bank_2mux_1w_1r_test.py diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 3a2d3f54..0f5deee2 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -877,9 +877,9 @@ class bank(design.design): if self.col_addr_size==0: return - bottom_inst = self.column_mux_array_inst[port] - top_inst = self.precharge_array_inst[port] - self.connect_bitlines(top_inst, bottom_inst, self.num_cols) + inst1 = self.column_mux_array_inst[port] + inst2 = self.precharge_array_inst[port] + self.connect_bitlines(inst1, inst2, self.num_cols) def route_column_mux_to_bitcell_array(self, port): """ Routing of BL and BR between col mux bitcell array """ @@ -888,47 +888,50 @@ class bank(design.design): if self.col_addr_size==0: return - bottom_inst = self.column_mux_array_inst[port] - top_inst = self.bitcell_array_inst - self.connect_bitlines(top_inst, bottom_inst, self.num_cols) + inst2 = self.column_mux_array_inst[port] + inst1 = self.bitcell_array_inst + inst1_bl_name = self.bl_names[port]+"_{}" + inst1_br_name = self.br_names[port]+"_{}" + self.connect_bitlines(inst1=inst1, inst2=inst2, num_bits=self.num_cols, + inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name) def route_sense_amp_to_column_mux_or_precharge_array(self, port): """ Routing of BL and BR between sense_amp and column mux or precharge array """ - bottom_inst = self.sense_amp_array_inst[port] + inst2 = self.sense_amp_array_inst[port] if self.col_addr_size>0: # Sense amp is connected to the col mux - top_inst = self.column_mux_array_inst[port] - top_bl = "bl_out_{}" - top_br = "br_out_{}" + inst1 = self.column_mux_array_inst[port] + inst1_bl_name = "bl_out_{}" + inst1_br_name = "br_out_{}" else: # Sense amp is directly connected to the precharge array - top_inst = self.precharge_array_inst[port] - top_bl = "bl_{}" - top_br = "br_{}" + inst1 = self.precharge_array_inst[port] + inst1_bl_name = "bl_{}" + inst1_br_name = "br_{}" - self.connect_bitlines(inst1=top_inst, inst2=bottom_inst, num_bits=self.word_size, - inst1_bl_name=top_bl, inst1_br_name=top_br) + self.connect_bitlines(inst1=inst1, inst2=inst2, num_bits=self.word_size, + inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name) - def route_write_driver_to_column_mux_or_precharge_array(self, port): - """ Routing of BL and BR between sense_amp and column mux or precharge array """ - bottom_inst = self.write_driver_array_inst[port] + def route_write_driver_to_column_mux_or_bitcell_array(self, port): + """ Routing of BL and BR between sense_amp and column mux or bitcell array """ + inst2 = self.write_driver_array_inst[port] if self.col_addr_size>0: - # Sense amp is connected to the col mux - top_inst = self.column_mux_array_inst[port] - top_bl = "bl_out_{}" - top_br = "br_out_{}" + # Write driver is connected to the col mux + inst1 = self.column_mux_array_inst[port] + inst1_bl_name = "bl_out_{}" + inst1_br_name = "br_out_{}" else: - # Sense amp is directly connected to the precharge array - top_inst = self.precharge_array_inst[port] - top_bl = "bl_{}" - top_br = "br_{}" + # Write driver is directly connected to the bitcell array + inst1 = self.bitcell_array_inst + inst1_bl_name = self.bl_names[port]+"_{}" + inst1_br_name = self.br_names[port]+"_{}" - self.connect_bitlines(inst1=top_inst, inst2=bottom_inst, num_bits=self.word_size, - inst1_bl_name=top_bl, inst1_br_name=top_br) + self.connect_bitlines(inst1=inst1, inst2=inst2, num_bits=self.word_size, + inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name) def route_write_driver_to_sense_amp(self, port): """ Routing of BL and BR between write driver and sense amp """ @@ -943,7 +946,7 @@ class bank(design.design): for bit in range(self.word_size): data_pin = self.sense_amp_array_inst[port].get_pin("data_{}".format(bit)) - self.add_layout_pin_rect_center(text="dout{0}_{1}".format(self.read_ports[port],bit), + self.add_layout_pin_rect_center(text="dout{0}_{1}".format(port,bit), layer=data_pin.layer, offset=data_pin.center(), height=data_pin.height(), diff --git a/compiler/tests/20_psram_1bank_2mux_1w_1r_test.py b/compiler/tests/20_psram_1bank_2mux_1w_1r_test.py new file mode 100755 index 00000000..223cb6ed --- /dev/null +++ b/compiler/tests/20_psram_1bank_2mux_1w_1r_test.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +""" +Run a regression test on a 1 bank SRAM +""" + +import unittest +from testutils import header,openram_test +import sys,os +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +from globals import OPTS +import debug + +#@unittest.skip("SKIPPING 20_psram_1bank_test, multiport layout not complete") +class psram_1bank_2mux_test(openram_test): + + def runTest(self): + globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + from sram import sram + from sram_config import sram_config + OPTS.bitcell = "pbitcell" + OPTS.replica_bitcell="replica_pbitcell" + + OPTS.num_rw_ports = 0 + OPTS.num_w_ports = 1 + OPTS.num_r_ports = 1 + + c = sram_config(word_size=4, + num_words=32, + num_banks=1) + c.num_words=32 + c.words_per_row=2 + debug.info(1, "Single bank two way column mux 1w/1r with control logic") + a = sram(c, "sram") + self.local_check(a, final_verification=True) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main() From 047d6ca2ef43e64c369a5e02d03d5158666ca4fe Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Fri, 16 Nov 2018 16:21:31 -0800 Subject: [PATCH 20/29] Must channel rout the column mux bits since they could overlap --- compiler/modules/bank.py | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 0f5deee2..8014b81c 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -930,8 +930,8 @@ class bank(design.design): inst1_bl_name = self.bl_names[port]+"_{}" inst1_br_name = self.br_names[port]+"_{}" - self.connect_bitlines(inst1=inst1, inst2=inst2, num_bits=self.word_size, - inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name) + self.channel_route_bitlines(inst1=inst1, inst2=inst2, num_bits=self.word_size, + inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name) def route_write_driver_to_sense_amp(self, port): """ Routing of BL and BR between write driver and sense amp """ @@ -972,6 +972,37 @@ class bank(design.design): din_name = "din{0}_{1}".format(port,row) self.copy_layout_pin(self.write_driver_array_inst[port], data_name, din_name) + def channel_route_bitlines(self, inst1, inst2, num_bits, + inst1_bl_name="bl_{}", inst1_br_name="br_{}", + inst2_bl_name="bl_{}", inst2_br_name="br_{}"): + """ + Route the bl and br of two modules using the channel router. + """ + + # determine top and bottom automatically. + # since they don't overlap, we can just check the bottom y coordinate. + if inst1.by() < inst2.by(): + (bottom_inst, bottom_bl_name, bottom_br_name) = (inst1, inst1_bl_name, inst1_br_name) + (top_inst, top_bl_name, top_br_name) = (inst2, inst2_bl_name, inst2_br_name) + else: + (bottom_inst, bottom_bl_name, bottom_br_name) = (inst2, inst2_bl_name, inst2_br_name) + (top_inst, top_bl_name, top_br_name) = (inst1, inst1_bl_name, inst1_br_name) + + + # Channel route each mux separately since we don't minimize the number + # of tracks in teh channel router yet. If we did, we could route all the bits at once! + offset = bottom_inst.ul() + vector(0,self.m1_pitch) + for bit in range(num_bits): + bottom_names = [bottom_bl_name.format(bit), bottom_br_name.format(bit)] + top_names = [top_bl_name.format(bit), top_br_name.format(bit)] + route_map = list(zip(bottom_names, top_names)) + bottom_pins = {key: bottom_inst.get_pin(key) for key in bottom_names } + top_pins = {key: top_inst.get_pin(key) for key in top_names } + all_pins = {**bottom_pins, **top_pins} + debug.check(len(all_pins)==len(bottom_pins)+len(top_pins),"Duplicate named pins in bitline channel route.") + self.create_horizontal_channel_route(route_map, all_pins, offset) + + def connect_bitlines(self, inst1, inst2, num_bits, inst1_bl_name="bl_{}", inst1_br_name="br_{}", inst2_bl_name="bl_{}", inst2_br_name="br_{}"): From c677efa217becd9d780b7e7c3c0da4ffa659cf31 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Sun, 18 Nov 2018 09:15:03 -0800 Subject: [PATCH 21/29] Fix control logic center location. Fix rail height error in write only control logic. --- compiler/modules/control_logic.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 31e239a4..98a2496d 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -45,7 +45,6 @@ class control_logic(design.design): def create_layout(self): """ Create layout and route between modules """ - self.route_rails() self.place_instances() self.route_all() @@ -149,7 +148,7 @@ class control_logic(design.design): def route_rails(self): """ Add the input signal inverted tracks """ - height = 4*self.inv1.height - self.m2_pitch + height = self.control_logic_center.y - self.m2_pitch offset = vector(self.ctrl_dff_array.width,0) self.rail_offsets = self.create_vertical_bus("metal2", self.m2_pitch, offset, self.internal_bus_list, height) @@ -182,21 +181,21 @@ class control_logic(design.design): row += 2 if (self.port_type == "rw") or (self.port_type == "w"): self.place_we_row(row=row) - pre_height = self.w_en_inst.uy() - control_center_y = self.w_en_inst.by() + height = self.w_en_inst.uy() + control_center_y = self.w_en_inst.uy() row += 1 if (self.port_type == "rw") or (self.port_type == "r"): self.place_rbl_in_row(row=row) self.place_sen_row(row=row+1) self.place_rbl(row=row+2) - pre_height = self.rbl_inst.uy() + height = self.rbl_inst.uy() control_center_y = self.rbl_inst.by() # This offset is used for placement of the control logic in the SRAM level. self.control_logic_center = vector(self.ctrl_dff_inst.rx(), control_center_y) # Extra pitch on top and right - self.height = pre_height + self.m3_pitch + self.height = height + self.m2_pitch # Max of modules or logic rows if (self.port_type == "rw") or (self.port_type == "r"): self.width = max(self.rbl_inst.rx(), max([inst.rx() for inst in self.row_end_inst])) + self.m2_pitch @@ -205,6 +204,7 @@ class control_logic(design.design): def route_all(self): """ Routing between modules """ + self.route_rails() self.route_dffs() if (self.port_type == "rw") or (self.port_type == "w"): self.route_wen() From ba8bec3f6794afefd75e82f8ec3e230c2f8e9fab Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Sun, 18 Nov 2018 09:30:27 -0800 Subject: [PATCH 22/29] Two m1 pitches at top of control logic --- compiler/modules/control_logic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 98a2496d..d227dfce 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -195,7 +195,7 @@ class control_logic(design.design): self.control_logic_center = vector(self.ctrl_dff_inst.rx(), control_center_y) # Extra pitch on top and right - self.height = height + self.m2_pitch + self.height = height + 2*self.m1_pitch # Max of modules or logic rows if (self.port_type == "rw") or (self.port_type == "r"): self.width = max(self.rbl_inst.rx(), max([inst.rx() for inst in self.row_end_inst])) + self.m2_pitch From 7709d5caa75e4bfc5c1e58729f09d218d3923ad9 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Sun, 18 Nov 2018 10:02:08 -0800 Subject: [PATCH 23/29] Move row addr dffs to top of bank to prevent addr route problems --- compiler/sram_1bank.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/sram_1bank.py b/compiler/sram_1bank.py index e1983ac5..896dcf6b 100644 --- a/compiler/sram_1bank.py +++ b/compiler/sram_1bank.py @@ -73,8 +73,9 @@ class sram_1bank(sram_base): self.control_logic_insts[port].place(control_pos[port]) # The row address bits are placed above the control logic aligned on the right. + # Or just below the top of the bank, whichever is greater. row_addr_pos[port] = vector(self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width, - self.control_logic_insts[port].uy()) + max(self.control_logic_insts[port].uy(), self.bank_inst.ul().y - self.row_addr_dff_insts[port].height)) self.row_addr_dff_insts[port].place(row_addr_pos[port]) # Add the col address flops below the bank to the left of the lower-left of bank array @@ -103,8 +104,9 @@ class sram_1bank(sram_base): self.control_logic_insts[port].place(control_pos[port], mirror="MY") # The row address bits are placed above the control logic aligned on the left. + # Or just below the top of the bank, whichever is greater. row_addr_pos[port] = vector(control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width, - self.control_logic_insts[port].uy()) + max(self.control_logic_insts[port].uy(), self.bank_inst.ul().y - self.row_addr_dff_insts[port].height)) self.row_addr_dff_insts[port].place(row_addr_pos[port], mirror="MY") # Add the col address flops above the bank to the right of the upper-right of bank array From 4630f52de2ffede1f5a2fba2f76a2280174b8135 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Mon, 19 Nov 2018 08:41:26 -0800 Subject: [PATCH 24/29] Use array ur instead of bank ur to pace row addr dff --- README.md | 4 ++-- compiler/modules/bank.py | 3 +-- compiler/sram_1bank.py | 14 ++++++++------ 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 03b7213f..91e00bbd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # OpenRAM -Stable: [![pipeline status](https://scone.soe.ucsc.edu:8888/mrg/PrivateRAM/badges/master/pipeline.svg?private_token=ynB6rSFLzvKUseoBPcwV)](https://github.com/VLSIDA/PrivateRAM/commits/master) -Unstable: [![pipeline status](https://scone.soe.ucsc.edu:8888/mrg/PrivateRAM/badges/dev/pipeline.svg?private_token=ynB6rSFLzvKUseoBPcwV)](https://github.com/VLSIDA/PrivateRAM/commits/dev) +Master: [![pipeline status](https://scone.soe.ucsc.edu:8888/mrg/PrivateRAM/badges/master/pipeline.svg?private_token=ynB6rSFLzvKUseoBPcwV)](https://github.com/VLSIDA/PrivateRAM/commits/master) +Dev: [![pipeline status](https://scone.soe.ucsc.edu:8888/mrg/PrivateRAM/badges/dev/pipeline.svg?private_token=ynB6rSFLzvKUseoBPcwV)](https://github.com/VLSIDA/PrivateRAM/commits/dev) [![Download](images/download.svg)](https://github.com/VLSIDA/PrivateRAM/archive/master.zip) [![License: BSD 3-clause](./images/license_badge.svg)](./LICENSE) diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 8014b81c..c4f4d557 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -65,8 +65,7 @@ class bank(design.design): self.bank_array_ur = self.bitcell_array_inst.ur() self.DRC_LVS() - - + def add_pins(self): """ Adding pins for Bank module""" for port in self.read_ports: diff --git a/compiler/sram_1bank.py b/compiler/sram_1bank.py index 896dcf6b..49ad4f9b 100644 --- a/compiler/sram_1bank.py +++ b/compiler/sram_1bank.py @@ -73,9 +73,10 @@ class sram_1bank(sram_base): self.control_logic_insts[port].place(control_pos[port]) # The row address bits are placed above the control logic aligned on the right. - # Or just below the top of the bank, whichever is greater. - row_addr_pos[port] = vector(self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width, - max(self.control_logic_insts[port].uy(), self.bank_inst.ul().y - self.row_addr_dff_insts[port].height)) + x_offset = self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width + # It is aove the control logic but below the top of the bitcell array + y_offset = max(self.control_logic_insts[port].uy(), self.bank.bank_array_ur.y - self.row_addr_dff_insts[port].height) + row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(row_addr_pos[port]) # Add the col address flops below the bank to the left of the lower-left of bank array @@ -104,9 +105,10 @@ class sram_1bank(sram_base): self.control_logic_insts[port].place(control_pos[port], mirror="MY") # The row address bits are placed above the control logic aligned on the left. - # Or just below the top of the bank, whichever is greater. - row_addr_pos[port] = vector(control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width, - max(self.control_logic_insts[port].uy(), self.bank_inst.ul().y - self.row_addr_dff_insts[port].height)) + x_offset = control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width + # It is above the control logic but below the top of the bitcell array + y_offset = max(self.control_logic_insts[port].uy(), self.bank.bank_array_ur.y - self.row_addr_dff_insts[port].height) + row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(row_addr_pos[port], mirror="MY") # Add the col address flops above the bank to the right of the upper-right of bank array From 6a7d721562d2804b4e85ffb26126eacca7c03308 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Mon, 19 Nov 2018 09:28:29 -0800 Subject: [PATCH 25/29] Add new bbox routine for pin enclosures --- compiler/base/pin_layout.py | 20 ++++++++++++++++++++ compiler/router/pin_group.py | 9 +++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/compiler/base/pin_layout.py b/compiler/base/pin_layout.py index c1e6d79a..417bd6af 100644 --- a/compiler/base/pin_layout.py +++ b/compiler/base/pin_layout.py @@ -62,6 +62,26 @@ class pin_layout: else: return False + def bbox(self, pin_list): + """ + Given a list of layout pins, create a bounding box layout. + """ + (ll, ur) = self.rect + min_x = ll.x + max_x = ur.x + min_y = ll.y + max_y = ur.y + + filtered_list = [x for x in pin_list if x != None] + debug.check(len(filtered_list)>0,"Cannot find bbox of empty list.") + for pin in filtered_list: + min_x = min(min_x, pin.ll().x) + max_x = max(max_x, pin.ur().x) + min_y = min(min_y, pin.ll().y) + max_y = max(max_y, pin.ur().y) + + self.rect = [vector(min_x,min_y),vector(max_x,max_y)] + def inflate(self, spacing=None): """ Inflate the rectangle by the spacing (or other rule) diff --git a/compiler/router/pin_group.py b/compiler/router/pin_group.py index 695a5432..bb147ab1 100644 --- a/compiler/router/pin_group.py +++ b/compiler/router/pin_group.py @@ -464,14 +464,15 @@ class pin_group: # If it is contained, it won't need a connector if pin.contained_by_any(self.enclosures): continue - + left_connector = self.find_left_connector(pin, self.enclosures) right_connector = self.find_right_connector(pin, self.enclosures) above_connector = self.find_above_connector(pin, self.enclosures) below_connector = self.find_below_connector(pin, self.enclosures) - for connector in [left_connector, right_connector, above_connector, below_connector]: - if connector: - self.enclosures.append(connector) + import copy + bbox_connector = copy.copy(pin) + bbox_connector.bbox([left_connector, right_connector, above_connector, below_connector]) + self.enclosures.append(bbox_connector) # Now, make sure each pin touches an enclosure. If not, add a connector. # This could only happen when there was no enclosure in any cardinal direction from a pin From a47509de26d3dd82f64bf059dce4c4bd79e8ab68 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Mon, 19 Nov 2018 15:42:22 -0800 Subject: [PATCH 26/29] Move via away from cell edges --- compiler/modules/hierarchical_predecode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/modules/hierarchical_predecode.py b/compiler/modules/hierarchical_predecode.py index 72d0d99f..85ead465 100644 --- a/compiler/modules/hierarchical_predecode.py +++ b/compiler/modules/hierarchical_predecode.py @@ -267,7 +267,7 @@ class hierarchical_predecode(design.design): # Find the x offsets for where the vias/pins should be placed in_xoffset = self.in_inst[0].rx() - out_xoffset = self.inv_inst[0].lx() + out_xoffset = self.inv_inst[0].lx() - self.m1_space for num in range(0,self.number_of_outputs): # this will result in duplicate polygons for rails, but who cares From 2694ee1a4c22febb077cd1e83b1c49194247ddd4 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Mon, 19 Nov 2018 15:43:19 -0800 Subject: [PATCH 27/29] Add all insufficient grids that overlap the pin at all --- compiler/router/router.py | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/compiler/router/router.py b/compiler/router/router.py index 727d7753..68ff2d00 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -523,35 +523,51 @@ class router(router_tech): zindex=self.get_zindex(pin.layer_num) for x in range(int(ll[0])+expansion,int(ur[0])+1+expansion): for y in range(int(ll[1]+expansion),int(ur[1])+1+expansion): - debug.info(4,"Converting [ {0} , {1} ]".format(x,y)) (full_overlap,partial_overlap) = self.convert_pin_coord_to_tracks(pin, vector3d(x,y,zindex)) if full_overlap: sufficient_list.update([full_overlap]) if partial_overlap: insufficient_list.update([partial_overlap]) + debug.info(4,"Converting [ {0} , {1} ] full={2} partial={3}".format(x,y, full_overlap, partial_overlap)) + if len(sufficient_list)>0: return sufficient_list elif expansion==0 and len(insufficient_list)>0: - #Remove blockages and return the best to be patched + #Remove blockages and return any overlap insufficient_list.difference_update(self.blocked_grids) - return self.get_best_offgrid_pin(pin, insufficient_list) + best_pin = self.get_all_offgrid_pin(pin, insufficient_list) + return best_pin elif expansion>0: #Remove blockages and return the nearest insufficient_list.difference_update(self.blocked_grids) - return self.get_nearest_offgrid_pin(pin, insufficient_list) + nearest_pin = self.get_nearest_offgrid_pin(pin, insufficient_list) + return nearest_pin else: debug.error("Unable to find any overlapping grids.", -1) + def get_all_offgrid_pin(self, pin, insufficient_list): + """ + Find a list of all pins with some overlap. + """ + #print("INSUFFICIENT LIST",insufficient_list) + # Find the coordinate with the most overlap + any_overlap = set() + for coord in insufficient_list: + full_pin = self.convert_track_to_pin(coord) + # Compute the overlap with that rectangle + overlap_rect=pin.compute_overlap(full_pin) + # Determine the max x or y overlap + max_overlap = max(overlap_rect) + if max_overlap>0: + any_overlap.update([coord]) + + return any_overlap + def get_best_offgrid_pin(self, pin, insufficient_list): """ - Given a pin and a list of partial overlap grids: - 1) Find the unblocked grids. - 2) If one, use it. - 3) If not, find the greatest overlap. - 4) Add a pin with the most overlap to make it "on grid" - that is not blocked. + Find a list of the single pin with the most overlap. """ #print("INSUFFICIENT LIST",insufficient_list) # Find the coordinate with the most overlap @@ -568,7 +584,7 @@ class router(router_tech): best_coord=coord return set([best_coord]) - + def get_nearest_offgrid_pin(self, pin, insufficient_list): """ Given a pin and a list of grid cells (probably non-overlapping), From 20d4e390f6ac35859dd2eac76c791d799b8ccfec Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Mon, 19 Nov 2018 15:45:07 -0800 Subject: [PATCH 28/29] Add bounding box of connector for when there are multiple connectors --- compiler/base/pin_layout.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/compiler/base/pin_layout.py b/compiler/base/pin_layout.py index 417bd6af..635366f9 100644 --- a/compiler/base/pin_layout.py +++ b/compiler/base/pin_layout.py @@ -72,9 +72,7 @@ class pin_layout: min_y = ll.y max_y = ur.y - filtered_list = [x for x in pin_list if x != None] - debug.check(len(filtered_list)>0,"Cannot find bbox of empty list.") - for pin in filtered_list: + for pin in pin_list: min_x = min(min_x, pin.ll().x) max_x = max(max_x, pin.ur().x) min_y = min(min_y, pin.ll().y) From b8299565eb588cb6e9df9bd15a9ef4c0ab7ee8f1 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Mon, 19 Nov 2018 17:32:55 -0800 Subject: [PATCH 29/29] Use grid furthest from blockages when blocked pin. Enclose multiple connectors. --- compiler/base/pin_layout.py | 2 +- compiler/router/grid_utils.py | 14 ++++++++++++++ compiler/router/pin_group.py | 24 ++++++++++++++++-------- compiler/router/router.py | 34 +++++++++++++++++++++++++--------- compiler/router/vector3d.py | 6 +++++- 5 files changed, 61 insertions(+), 19 deletions(-) diff --git a/compiler/base/pin_layout.py b/compiler/base/pin_layout.py index 635366f9..8f9e81ee 100644 --- a/compiler/base/pin_layout.py +++ b/compiler/base/pin_layout.py @@ -343,7 +343,7 @@ class pin_layout: (r2_ll,r2_ur) = other.rect def dist(x1, y1, x2, y2): - return sqrt((x2-x1)**2 + (y2-y1)**2) + return math.sqrt((x2-x1)**2 + (y2-y1)**2) left = r2_ur.x < r1_ll.x right = r1_ur.x < r2_ll.x diff --git a/compiler/router/grid_utils.py b/compiler/router/grid_utils.py index 23fe23ea..7ad864aa 100644 --- a/compiler/router/grid_utils.py +++ b/compiler/router/grid_utils.py @@ -3,6 +3,7 @@ Some utility functions for sets of grid cells. """ import debug +import math from direction import direction from vector3d import vector3d @@ -139,3 +140,16 @@ def flatten_set(curset): else: newset.update(flatten_set(c)) return newset + + + +def distance_set(coord, curset): + """ + Return the distance from a coordinate to any item in the set + """ + min_dist = math.inf + for c in curset: + min_dist = min(coord.euclidean_distance(c), min_dist) + + return min_dist + diff --git a/compiler/router/pin_group.py b/compiler/router/pin_group.py index bb147ab1..e50f94d9 100644 --- a/compiler/router/pin_group.py +++ b/compiler/router/pin_group.py @@ -465,16 +465,22 @@ class pin_group: if pin.contained_by_any(self.enclosures): continue + # Find a connector in the cardinal directions + # If there is overlap, but it isn't contained, these could all be None + # These could also be none if the pin is diagonal from the enclosure left_connector = self.find_left_connector(pin, self.enclosures) right_connector = self.find_right_connector(pin, self.enclosures) above_connector = self.find_above_connector(pin, self.enclosures) below_connector = self.find_below_connector(pin, self.enclosures) - import copy - bbox_connector = copy.copy(pin) - bbox_connector.bbox([left_connector, right_connector, above_connector, below_connector]) - self.enclosures.append(bbox_connector) + connector_list = [left_connector, right_connector, above_connector, below_connector] + filtered_list = list(filter(lambda x: x!=None, connector_list)) + if (len(filtered_list)>0): + import copy + bbox_connector = copy.copy(pin) + bbox_connector.bbox(filtered_list) + self.enclosures.append(bbox_connector) - # Now, make sure each pin touches an enclosure. If not, add a connector. + # Now, make sure each pin touches an enclosure. If not, add another (diagonal) connector. # This could only happen when there was no enclosure in any cardinal direction from a pin for pin_list in self.pins: if not self.overlap_any_shape(pin_list, self.enclosures): @@ -596,7 +602,7 @@ class pin_group: # At least one of the groups must have some valid tracks if (len(pin_set)==0 and len(blockage_set)==0): - debug.warning("Pin is very close to metal blockage.\nAttempting to expand blocked pin {}".format(self.pins)) + #debug.warning("Pin is very close to metal blockage.\nAttempting to expand blocked pin {}".format(self.pins)) for pin_list in self.pins: for pin in pin_list: @@ -604,7 +610,7 @@ class pin_group: # Determine which tracks the pin overlaps pin_in_tracks=self.router.convert_pin_to_tracks(self.name, pin, expansion=1) pin_set.update(pin_in_tracks) - + if len(pin_set)==0: debug.error("Unable to find unblocked pin {} {}".format(self.name, self.pins)) self.router.write_debug_gds("blocked_pin.gds") @@ -650,7 +656,6 @@ class pin_group: that is ensured to overlap the supply rail wire. It then adds rectangle(s) for the enclosure. """ - additional_set = set() # Check the layer of any element in the pin to determine which direction to route it e = next(iter(start_set)) @@ -674,3 +679,6 @@ class pin_group: self.set_routed() self.enclosures = self.compute_enclosures() + + + diff --git a/compiler/router/router.py b/compiler/router/router.py index 68ff2d00..bb6e1efc 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -515,7 +515,7 @@ class router(router_tech): # scale the size bigger to include neaby tracks ll=ll.scale(self.track_factor).floor() ur=ur.scale(self.track_factor).ceil() - + #print(pin) # Keep tabs on tracks with sufficient and insufficient overlap sufficient_list = set() insufficient_list = set() @@ -529,23 +529,22 @@ class router(router_tech): if partial_overlap: insufficient_list.update([partial_overlap]) debug.info(4,"Converting [ {0} , {1} ] full={2} partial={3}".format(x,y, full_overlap, partial_overlap)) - + # Remove the blocked grids + sufficient_list.difference_update(self.blocked_grids) + insufficient_list.difference_update(self.blocked_grids) + if len(sufficient_list)>0: return sufficient_list elif expansion==0 and len(insufficient_list)>0: - #Remove blockages and return any overlap - insufficient_list.difference_update(self.blocked_grids) best_pin = self.get_all_offgrid_pin(pin, insufficient_list) + #print(best_pin) return best_pin elif expansion>0: - #Remove blockages and return the nearest - insufficient_list.difference_update(self.blocked_grids) - nearest_pin = self.get_nearest_offgrid_pin(pin, insufficient_list) + nearest_pin = self.get_furthest_offgrid_pin(pin, insufficient_list) return nearest_pin else: - debug.error("Unable to find any overlapping grids.", -1) - + return set() def get_all_offgrid_pin(self, pin, insufficient_list): """ @@ -585,6 +584,23 @@ class router(router_tech): return set([best_coord]) + def get_furthest_offgrid_pin(self, pin, insufficient_list): + """ + Get a grid cell that is the furthest from the blocked grids. + """ + + #print("INSUFFICIENT LIST",insufficient_list) + # Find the coordinate with the most overlap + best_coord = None + best_dist = math.inf + for coord in insufficient_list: + min_dist = grid_utils.distance_set(coord, self.blocked_grids) + if min_dist