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/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/base/pin_layout.py b/compiler/base/pin_layout.py index c1e6d79a..8f9e81ee 100644 --- a/compiler/base/pin_layout.py +++ b/compiler/base/pin_layout.py @@ -62,6 +62,24 @@ 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 + + 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) + 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) @@ -325,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/modules/bank.py b/compiler/modules/bank.py index f10cb045..41eb6483 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: @@ -877,9 +876,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 +887,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.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 """ @@ -943,7 +945,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(), @@ -969,6 +971,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_{}"): diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 5f751be8..7ca6b10d 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -52,7 +52,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() @@ -266,7 +265,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) @@ -299,21 +298,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 + 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 @@ -322,6 +321,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() 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 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 695a5432..e50f94d9 100644 --- a/compiler/router/pin_group.py +++ b/compiler/router/pin_group.py @@ -464,16 +464,23 @@ class pin_group: # If it is contained, it won't need a connector 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) - for connector in [left_connector, right_connector, above_connector, below_connector]: - if connector: - self.enclosures.append(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): @@ -595,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: @@ -603,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") @@ -649,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)) @@ -673,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 727d7753..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() @@ -523,35 +523,50 @@ 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)) + # 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 the best to be patched - 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) + #print(best_pin) + 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_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): + """ + 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,6 +583,23 @@ class router(router_tech): best_coord=coord 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_dist0: - # self.control_logic_rw = self.mod_control_logic(num_rows=self.num_rows, - # words_per_row=self.words_per_row, - # port_type="rw") - # self.add_mod(self.control_logic_rw) - # if len(self.write_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: - # self.control_logic_r = self.mod_control_logic(num_rows=self.num_rows, - # words_per_row=self.words_per_row, - # port_type="r") - # self.add_mod(self.control_logic_r) -# >>>>>>> dev # Create the address and control flops (but not the clk) from dff_array import dff_array @@ -283,13 +260,13 @@ class sram_base(design): sram=self, 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, sram=self, 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, sram=self, 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()