diff --git a/README.md b/README.md index e6a20328..91e00bbd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # OpenRAM -[![pipeline status](https://scone.soe.ucsc.edu:8888/mrg/PrivateRAM/badges/dev/pipeline.svg?private_token=ynB6rSFLzvKUseoBPcwV)](https://github.com/VLSIDA/PrivateRAM/commits) -[![Download](images/download.svg)](https://github.com/VLSIDA/PrivateRAM/archive/dev.zip) +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) An open-source static random access memory (SRAM) compiler. @@ -38,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 @@ -55,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: @@ -67,7 +73,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"] @@ -87,7 +93,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 @@ -175,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. @@ -184,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 @@ -192,9 +200,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 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..f52aa100 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") @@ -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/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 91e11280..6031fe2f 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -35,20 +35,24 @@ 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', + 'pbitcell', + 'replica_pbitcell', '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/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) 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/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/bank.py b/compiler/modules/bank.py index f2226797..c4f4d557 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -61,11 +61,11 @@ 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() - - + def add_pins(self): """ Adding pins for Bank module""" for port in self.read_ports: @@ -232,8 +232,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: @@ -290,8 +290,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) @@ -723,7 +726,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) @@ -873,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 """ @@ -884,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 """ @@ -939,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(), @@ -965,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_{}"): @@ -1189,8 +1226,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/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/control_logic.py b/compiler/modules/control_logic.py index 31e239a4..d227dfce 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 + 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 @@ -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() 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 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/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 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 d71c7073..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): @@ -495,6 +502,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. @@ -585,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: @@ -593,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") @@ -639,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)) @@ -663,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 18d88df7..bb6e1efc 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: @@ -507,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() @@ -515,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 @@ -560,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_dist1: + # 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. + 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 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) + 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 below the bank to the right of the center of bank: - # This relies on the center point of the bank: + # 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. - 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 + 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 +152,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,20 +189,27 @@ 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]) - # This uses a metal2 track to the right of the control/row addr DFF - # to route vertically. + 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 (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 diff --git a/compiler/sram_base.py b/compiler/sram_base.py index 74220e63..29c3cbb9 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() @@ -225,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") @@ -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/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() 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() 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() diff --git a/docs/figs/sense_amp_schem.pdf b/docs/figs/sense_amp_schem.pdf index 0b062ffe..01c202be 100644 Binary files a/docs/figs/sense_amp_schem.pdf and b/docs/figs/sense_amp_schem.pdf differ diff --git a/docs/figs/sense_amp_schem.svg b/docs/figs/sense_amp_schem.svg index 749b6803..946aac45 100644 --- a/docs/figs/sense_amp_schem.svg +++ b/docs/figs/sense_amp_schem.svg @@ -1,568 +1,113 @@ - - - - - - - - - - - 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 + + + + + + + + + + + + + + + + +