diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..5a8c6f66 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,14 @@ +[run] +omit = + # omit anything in a .local directory anywhere + */.local/* + # omit everything in /usr + /usr/* +[paths] +source = + /home/gitlab-runner/builds/2fd64746/0 + /home/gitlab-runner/builds/2fd64746/1 + /home/gitlab-runner/builds/2fd64746/2 + /home/gitlab-runner/builds/2fd64746/3 + /home/gitlab-runner/builds/2fd64746/4 + /home/gitlab-runner/builds/2fd64746/5 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 96e30d2d..27c341aa 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,39 @@ +before_script: + - . /home/gitlab-runner/setup-paths.sh + - export OPENRAM_HOME="`pwd`/compiler" + - export OPENRAM_TECH="`pwd`/technology" + +stages: + - test + - coverage + freepdk45: - script: "/home/gitlab-runner/regress_freepdk45.sh" + stage: test + script: + - coverage run -p $OPENRAM_HOME/tests/regress.py -t freepdk45 + artifacts: + paths: + - .coverage.* + expire_in: 1 week scn4m_subm: - script: "/home/gitlab-runner/regress_scn4m_subm.sh" + stage: test + script: + - coverage run -p $OPENRAM_HOME/tests/regress.py -t scn4m_subm + artifacts: + paths: + - .coverage.* + expire_in: 1 week + +coverage: + stage: coverage + script: + - coverage combine + - coverage report + - coverage html -d coverage_html + artifacts: + paths: + - coverage_html + expire_in: 1 week + coverage: '/TOTAL.+ ([0-9]{1,3}%)/' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 582a998e..dacf53e4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,6 +7,26 @@ list at openram-dev-group@ucsc.edu. We are happy to give insights into the best way to implement a change to ensure your contribution will be accepted and help other OpenRAM users. +# Directory Structure + +* compiler - openram compiler itself (pointed to by OPENRAM_HOME) + * compiler/base - base data structure modules + * compiler/pgates - parameterized cells (e.g. logic gates) + * compiler/bitcells - various bitcell styles + * compiler/modules - high-level modules (e.g. decoders, etc.) + * compiler/verify - DRC and LVS verification wrappers + * compiler/characterizer - timing characterization code + * compiler/gdsMill - GDSII reader/writer + * compiler/router - router for signals and power supplies + * compiler/tests - unit tests +* technology - openram technology directory (pointed to by OPENRAM_TECH) + * technology/freepdk45 - example configuration library for [FreePDK45 technology node + * technology/scn4m_subm - example configuration library [SCMOS] technology node + * technology/scn3me_subm - unsupported configuration (not enough metal layers) + * technology/setup_scripts - setup scripts to customize your PDKs and OpenRAM technologies +* docs - LaTeX manual (outdated) +* lib - IP library of pregenerated memories + # Code Style Our code may not be the best and we acknowledge that. We welcome diff --git a/README.md b/README.md index 1a193703..693d32ef 100644 --- a/README.md +++ b/README.md @@ -1,83 +1,119 @@ # 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) + +[![Python 3.5](https://img.shields.io/badge/Python-3.5-green.svg)](https://www.python.org/) [![License: BSD 3-clause](./images/license_badge.svg)](./LICENSE) +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) +![Coverage](https://scone.soe.ucsc.edu:8888/mrg/PrivateRAM/badges/master/coverage.svg?private_token=ynB6rSFLzvKUseoBPcwV) +[![Download](./images/download-stable-blue.svg)](https://github.com/VLSIDA/PrivateRAM/archive/master.zip) + +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) +![Coverage](https://scone.soe.ucsc.edu:8888/mrg/PrivateRAM/badges/dev/coverage.svg?private_token=ynB6rSFLzvKUseoBPcwV) +[![Download](./images/download-unstable-blue.svg)](https://github.com/VLSIDA/PrivateRAM/archive/dev.zip) + An open-source static random access memory (SRAM) compiler. -# Why OpenRAM? +# What is OpenRAM? + + +OpenRAM is an open-source Python framework to create the layout, +netlists, timing and power models, placement and routing models, and +other views necessary to use SRAMs in ASIC design. OpenRAM supports +integration in both commercial and open-source flows with both +predictive and fabricable technologies. # Basic Setup The OpenRAM compiler has very few dependencies: + [Ngspice] 26 (or later) or HSpice I-2013.12-1 (or later) or CustomSim 2017 (or later) -+ Python 3.5 and higher ++ Python 3.5 or higher + Python numpy (pip3 install numpy to install) + flask_table (pip3 install flask to install) If you want to perform DRC and LVS, you will need either: -+ Calibre (for [FreePDK45] or [SCMOS]) -+ [Magic] + [Netgen] (for [SCMOS] only) ++ Calibre (for [FreePDK45]) ++ [Magic] + [Netgen] (for [SCMOS]) + +You must set two environment variables: ++ OPENRAM\_HOME should point to the compiler source directory. ++ OPENERAM\_TECH should point to a root technology directory. + +For example add this to your .bashrc: -You must set two environment variables: OPENRAM\_HOME should point to -the compiler source directory. OPENERAM\_TECH should point to a root -technology directory that contains subdirs of all other technologies. -For example, in bash, add to your .bashrc: ``` export OPENRAM_HOME="$HOME/openram/compiler" export OPENRAM_TECH="$HOME/openram/technology" ``` -For example, in csh/tcsh, add to your .cshrc/.tcshrc: + +You may also wish to add OPENRAM\_HOME to your PYTHONPATH: + ``` - setenv OPENRAM_HOME "$HOME/openram/compiler" - setenv OPENRAM_TECH "$HOME/openram/technology" + export PYTHONPATH="$PYTHONPATH:$OPENRAM_HOME" ``` -We include the tech files necessary for [FreePDK45] and [SCMOS]. The [SCMOS] -spice models, however, are generic and should be replaced with foundry -models. -If you are using [FreePDK45], you should also have that set up and have the -environment variable point to the PDK. -For example, in bash, add to your .bashrc: +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 +should also have that set up and have the environment variable point +to the PDK. For example add this to your .bashrc: + ``` export FREEPDK45="/bsoe/software/design-kits/FreePDK45" ``` -For example, in csh/tcsh, add to your .tcshrc: -``` - setenv FREEPDK45 "/bsoe/software/design-kits/FreePDK45" -``` -We do not distribute the PDK, but you may download [FreePDK45] +You may get the entire [FreePDK45 PDK here][FreePDK45]. If you are using [SCMOS], you should install [Magic] and [Netgen]. -We have included the SCN4M design rules from [Qflow]. +We have included the most recent SCN4M_SUBM design rules from [Qflow]. -# Directory Structure +# Basic Usage -* compiler - openram compiler itself (pointed to by OPENRAM_HOME) - * compiler/base - base data structure modules - * compiler/pgates - parameterized cells (e.g. logic gates) - * compiler/bitcells - various bitcell styles - * compiler/modules - high-level modules (e.g. decoders, etc.) - * compiler/verify - DRC and LVS verification wrappers - * compiler/characterizer - timing characterization code - * compiler/gdsMill - GDSII reader/writer - * compiler/router - router for signals and power supplies - * compiler/tests - unit tests -* technology - openram technology directory (pointed to by OPENRAM_TECH) - * technology/freepdk45 - example configuration library for [FreePDK45 technology node - * technology/scn4m_subm - example configuration library [SCMOS] technology node - * technology/scn3me_subm - unsupported configuration (not enough metal layers) - * technology/setup_scripts - setup scripts to customize your PDKs and OpenRAM technologies -* docs - LaTeX manual (outdated) -* lib - IP library of pregenerated memories +Once you have defined the environment, you can run OpenRAM from the command line +using a single configuration file written in Python. + +For example, create a file called *myconfig.py* specifying the following +parameters for your memory: + +``` +# Data word size +word_size = 2 +# Number of words in the memory +num_words = 16 + +# Technology to use in $OPENRAM_TECH +tech_name = "scn4m_subm" +# Process corners to characterize +process_corners = ["TT"] +# Voltage corners to characterize +supply_voltages = [ 3.3 ] +# Temperature corners to characterize +temperatures = [ 25 ] + +# Output directory for the results +output_path = "temp" +# Output file base name +output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name) + +# Disable analytical models for full characterization (WARNING: slow!) +# analytical_delay = False +``` + +You can then run OpenRAM by executing: +``` +python3 $OPENRAM_HOME/openram.py myconfig +``` +You can see all of the options for the configuration file in +$OPENRAM\_HOME/options.py # Unit Tests Regression testing performs a number of tests for all modules in OpenRAM. +From the unit test directory ($OPENRAM\_HOME/tests), +use the following command to run all regression tests: -Use the command: ``` python3 regress.py ``` @@ -96,68 +132,100 @@ To specify a particular technology use "-t " such as The default for openram.py is specified in the configuration file. -# Creating Custom Technologies +# Porting to a New Technology If you want to support a enw technology, you will need to create: + a setup script for each technology you want to use + a technology directory for each technology with the base cells All setup scripts should be in the setup\_scripts directory under the -$OPENRAM\_TECH directory. We provide two technology examples for [SCMOS] and [FreePDK45]. -Please look at the following file for an example of what is needed for OpenRAM: +$OPENRAM\_TECH directory. We provide two technology examples for +[SCMOS] and [FreePDK45]. Please look at the following file for an +example of what is needed for OpenRAM: + ``` $OPENRAM_TECH/setup_scripts/setup_openram_freepdk45.py ``` + Each setup script should be named as: setup\_openram\_{tech name}.py. Each specific technology (e.g., [FreePDK45]) should be a subdirectory (e.g., $OPENRAM_TECH/freepdk45) and include certain folders and files: - 1. gds_lib folder with all the .gds (premade) library cells. At a - minimum this includes: - * ms_flop.gds - * sense_amp.gds - * write_driver.gds - * cell_6t.gds - * replica_cell_6t.gds - * tri_gate.gds - 2. sp_lib folder with all the .sp (premade) library netlists for the above cells. - 3. layers.map - 4. A valid tech Python module (tech directory with __init__.py and tech.py) with: - * References in tech.py to spice models - * DRC/LVS rules needed for dynamic cells and routing - * Layer information - * etc. +* gds_lib folder with all the .gds (premade) library cells: + * dff.gds + * sense_amp.gds + * write_driver.gds + * cell_6t.gds + * replica\_cell\_6t.gds +* sp_lib folder with all the .sp (premade) library netlists for the above cells. +* layers.map +* A valid tech Python module (tech directory with __init__.py and tech.py) with: + * References in tech.py to spice models + * DRC/LVS rules needed for dynamic cells and routing + * Layer information + * Spice and supply information + * etc. # Get Involved -+ Report bugs by submitting a [Github issue]. ++ Report bugs by submitting [Github issues]. + Develop new features (see [how to contribute](./CONTRIBUTING.md)) + Submit code/fixes using a [Github pull request] + Follow our [project][Github projects]. + Read and cite our [ICCAD paper][OpenRAMpaper] +# Further Help + ++ [Additional hints](./HINTS.md) ++ [OpenRAM Slack Workspace][Slack] ++ [OpenRAM Users Group][user-group] ([subscribe here][user-group-subscribe]) ++ [OpenRAM Developers Group][dev-group] ([subscribe here][dev-group-subscribe]) + # License OpenRAM is licensed under the [BSD 3-clause License](./LICENSE). # Contributors & Acknowledgment -- [Matthew Guthaus][Matthew Guthaus] created the OpenRAM project and is the lead architect. +- [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 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. +- Bin Wu created early parameterized cells. +- Yusu Wang is porting parameterized cells to new technologies. +- 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 -[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 -[email me]: mailto:mrg+openram@ucsc.edu +[James Stine]: https://ece.okstate.edu/content/stine-james-e-jr-phd [VLSIDA]: https://vlsida.soe.ucsc.edu -[OSUPDK]: https://vlsiarch.ecen.okstate.edu/flow/ +[VLSIARCH]: https://vlsiarch.ecen.okstate.edu/ +[OpenRAMpaper]: https://ieeexplore.ieee.org/document/7827670/ + +[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 +[user-group]: mailto:openram-user-group@ucsc.edu +[dev-group-subscribe]: mailto:openram-dev-group+subscribe@ucsc.edu +[user-group-subscribe]: mailto:openram-user-group+subscribe@ucsc.edu + [Magic]: http://opencircuitdesign.com/magic/ [Netgen]: http://opencircuitdesign.com/netgen/ [Qflow]: http://opencircuitdesign.com/qflow/history.html +[Ngspice]: http://ngspice.sourceforge.net/ + +[OSUPDK]: https://vlsiarch.ecen.okstate.edu/flow/ [FreePDK45]: https://www.eda.ncsu.edu/wiki/FreePDK45:Contents [SCMOS]: https://www.mosis.com/files/scmos/scmos.pdf -[Ngspice]: http://ngspice.sourceforge.net/ -[OpenRAMpaper]: https://ieeexplore.ieee.org/document/7827670/ + +[Slack]: https://join.slack.com/t/openram/shared_invite/enQtNDgxMjc3NzU5NTI1LTE4ODMyM2I0Mzk2ZmFiMjgwYTYyMTQ4NTgwMmUwMDhiM2E1MDViNDRjYzU1NjJhZTQxNWZjMzE3M2FlODBmZjA 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 5948d066..68749693 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -34,20 +34,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 + + + + + + + + + + + + + + + + + diff --git a/images/Python-3.5-green.svg b/images/Python-3.5-green.svg new file mode 100644 index 00000000..5856e0ee --- /dev/null +++ b/images/Python-3.5-green.svg @@ -0,0 +1 @@ + PythonPython3.53.5 \ No newline at end of file diff --git a/images/SCMOS_16kb_sram.jpg b/images/SCMOS_16kb_sram.jpg new file mode 100644 index 00000000..0dda0b08 Binary files /dev/null and b/images/SCMOS_16kb_sram.jpg differ diff --git a/images/download-stable-blue.svg b/images/download-stable-blue.svg new file mode 100644 index 00000000..2fbc3649 --- /dev/null +++ b/images/download-stable-blue.svg @@ -0,0 +1 @@ + downloaddownloadstablestable \ No newline at end of file diff --git a/images/download-unstable-blue.svg b/images/download-unstable-blue.svg new file mode 100644 index 00000000..a233df6b --- /dev/null +++ b/images/download-unstable-blue.svg @@ -0,0 +1 @@ + downloaddownloadunstableunstable \ No newline at end of file diff --git a/images/download.svg b/images/download.svg deleted file mode 100644 index 95d978ed..00000000 --- a/images/download.svg +++ /dev/null @@ -1,2 +0,0 @@ - -download download latestlatest