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/HINTS.md b/HINTS.md new file mode 100644 index 00000000..b12704e5 --- /dev/null +++ b/HINTS.md @@ -0,0 +1,110 @@ +# Debugging + +When OpenRAM runs, it puts files in a temporary directory that is +shown in the banner at the top. Like: +``` + /tmp/openram_mrg_18128_temp/ +``` +This is where simulations and DRC/LVS get run so there is no network +traffic. The directory name is unique for each person and run of +OpenRAM to not clobber any files and allow simultaneous runs. If it +passes, the files are deleted. If it fails, you will see these files: ++ temp.gds is the layout (.mag files too if using SCMOS) ++ temp.sp is the netlist ++ test1.drc.err is the std err output of the DRC command ++ test1.drc.out is the standard output of the DRC command ++ test1.drc.results is the DRC results file ++ test1.lvs.err is the std err output of the LVS command ++ test1.lvs.out is the standard output of the LVS command ++ test1.lvs.results is the DRC results file + +Depending on your DRC/LVS tools, there will also be: ++ \_calibreDRC.rul\_ is the DRC rule file (Calibre) ++ dc_runset is the command file (Calibre) ++ extracted.sp (Calibre) ++ run_lvs.sh is a Netgen script for LVS (Netgen) ++ run_drc.sh is a Magic script for DRC (Magic) ++ .spice (Magic) + +If DRC/LVS fails, the first thing is to check if it ran in the .out and +.err file. This shows the standard output and error output from +running DRC/LVS. If there is a setup problem it will be shown here. + +If DRC/LVS runs, but doesn't pass, you then should look at the .results +file. If the DRC fails, it will typically show you the command that was used +to run Calibre or Magic+Netgen. + +To debug, you will need a layout viewer. I prefer to use Glade +on my Mac, but you can also use Calibre, Magic, etc. + +1. Calibre + + Start the Calibre DESIGNrev viewer in the temp directory and load your GDS file: +``` + calibredrv temp.gds +``` + Select Verification->Start RVE and select the results database file in + the new form (e.g., test1.drc.db). This will start the RVE (results + viewer). Scroll through the check pane and find the DRC check with an + error. Select it and it will open some numbers to the right. Double + click on any of the errors in the result browser. These will be + labelled as numbers "1 2 3 4" for example will be 4 DRC errors. + + In the viewer ">" opens the layout down a level. + +2. Glade + + You can view errors in Glade as well. I like this because it is on my laptop. + You can get it from: http://www.peardrop.co.uk/glade/ + + To remote display over X windows, you need to disable OpenGL acceleration or use vnc + or something. You can disable by adding this to your .bashrc in bash: +``` + export GLADE_USE_OPENGL=no +``` + or in .cshrc/.tcshrc in csh/tcsh: +``` + setenv GLADE_USE_OPENGAL no +``` + To use this with the FreePDK45 or SCMOS layer views you should use the + tech files. Then create a .glade.py file in your user directory with + these commands to load the technology layers: +``` +ui().importCds("default", +"/Users/mrg/techfiles/freepdk45/display.drf", +"/Users/mrg/techfiles/freepdk45/FreePDK45.tf", 1000, 1, +"/Users/mrg/techfiles/freepdk45/layers.map") +``` + Obviously, edit the paths to point to your directory. To switch + between processes, you have to change the importCds command (or you + can manually run the command each time you start glade). + + To load the errors, you simply do Verify->Import Calibre Errors select + the .results file from Calibre. + +3. Magic + + Magic is only supported in SCMOS. You will need to install the MOSIS SCMOS rules + and Magic from: http://opencircuitdesign.com/ + + When running DRC or extraction, OpenRAM will load the GDS file, save + the .ext/.mag files, and export an extracted netlist (.spice). + +4. It is possible to use other viewers as well, such as: + * LayoutEditor http://www.layouteditor.net/ + + +# Example to output/input .gds layout files from/to Cadence + +1. To create your component layouts, you should stream them to + individual gds files using our provided layermap and flatten + cells. For example, +``` + strmout -layerMap layers.map -library sram -topCell $i -view layout -flattenVias -flattenPcells -strmFile ../gds_lib/$i.gds +``` +2. To stream a layout back into Cadence, do this: +``` + strmin -layerMap layers.map -attachTechFileOfLib NCSU\_TechLib\_FreePDK45 -library sram_4_32 -strmFile sram_4_32.gds +``` + When you import a gds file, make sure to attach the correct tech lib + or you will get incorrect layers in the resulting library. diff --git a/README.md b/README.md index b486823b..e6a20328 100644 --- a/README.md +++ b/README.md @@ -1,238 +1,214 @@ -# BASIC SETUP +# 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) +[![License: BSD 3-clause](./images/license_badge.svg)](./LICENSE) -Please look at the OpenRAM ICCAD paper and presentation in the repository: -https://github.com/mguthaus/OpenRAM/blob/master/OpenRAM_ICCAD_2016_paper.pdf -https://github.com/mguthaus/OpenRAM/blob/master/OpenRAM_ICCAD_2016_presentation.pdf +An open-source static random access memory (SRAM) compiler. + +# 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 numpy (pip3 install numpy to install) -* flask_table (pip3 install flask to install) -* a setup script for each technology you want to use -* a technology directory for each technology with the base cells ++ [Ngspice] 26 (or later) or HSpice I-2013.12-1 (or later) or CustomSim 2017 (or later) ++ 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: -``` - setenv OPENRAM_HOME "$HOME/openram/compiler" - setenv OPENRAM_TECH "$HOME/openram/technology" -``` -We include the tech files necessary for FreePDK and SCMOS. The SCMOS -spice models, however, are generic and should be replaced with foundry -models. -If you are using FreePDK, 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: + +You may get the entire [FreePDK45 PDK here][FreePDK45]. +If you are using [SCMOS], you should install [Magic] and [Netgen]. +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. + +For example, create a file called *myconfig.py* specifying the following +parameters for your memory: + ``` - setenv FREEPDK45 "/bsoe/software/design-kits/FreePDK45" +# 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 ``` -We do not distribute the PDK, but you may get it from: - https://www.eda.ncsu.edu/wiki/FreePDK45:Contents -If you are using SCMOS, you should install Magic and netgen from: - http://opencircuitdesign.com/magic/ - http://opencircuitdesign.com/netgen/ -We have included the SCN4M design rules from QFlow: - http://opencircuitdesign.com/qflow/ - -# 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 +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 +# 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: ``` - python regress.py + python3 regress.py ``` To run a specific test: ``` - python {unit test}.py + python3 {unit test}.py ``` The unit tests take the same arguments as openram.py itself. To increase the verbosity of the test, add one (or more) -v options: ``` - python tests/00_code_format_check_test.py -v -t freepdk45 + python3 tests/00_code_format_check_test.py -v -t freepdk45 ``` To specify a particular technology use "-t " such as -"-t freepdk45" or "-t scn4m_subm". The default for a unit test is scn4m_subm. +"-t freepdk45" or "-t scn4m\_subm". The default for a unit test is scn4m_subm. The default for openram.py is specified in the configuration file. -# CREATING CUSTOM TECHNOLOGIES +# Porting to a New Technology -All setup scripts should be in the setup_scripts directory under the -$OPENRAM_TECH directory. Please look at the following file for an +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/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 +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. -# DEBUGGING +# Get Involved -When OpenRAM runs, it puts files in a temporary directory that is -shown in the banner at the top. Like: -``` - /tmp/openram_mrg_18128_temp/ -``` -This is where simulations and DRC/LVS get run so there is no network -traffic. The directory name is unique for each person and run of -OpenRAM to not clobber any files and allow simultaneous runs. If it -passes, the files are deleted. If it fails, you will see these files: -* temp.gds is the layout -* (.mag files if using SCMOS) -* temp.sp is the netlist -* test1.drc.err is the std err output of the DRC command -* test1.drc.out is the standard output of the DRC command -* test1.drc.results is the DRC results file -* test1.lvs.err is the std err output of the LVS command -* test1.lvs.out is the standard output of the LVS command -* test1.lvs.results is the DRC results file ++ 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] -Depending on your DRC/LVS tools, there will also be: -* _calibreDRC.rul_ is the DRC rule file (Calibre) -* dc_runset is the command file (Calibre) -* extracted.sp (Calibre) -* run_lvs.sh is a Netgen script for LVS (Netgen) -* run_drc.sh is a Magic script for DRC (Magic) -* .spice (Magic) +# Further Help -If DRC/LVS fails, the first thing is to check if it ran in the .out and -.err file. This shows the standard output and error output from -running DRC/LVS. If there is a setup problem it will be shown here. ++ [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]) -If DRC/LVS runs, but doesn't pass, you then should look at the .results -file. If the DRC fails, it will typically show you the command that was used -to run Calibre or Magic+Netgen. +# License -To debug, you will need a layout viewer. I prefer to use Glade -on my Mac, but you can also use Calibre, Magic, etc. +OpenRAM is licensed under the [BSD 3-clause License](./LICENSE). -1. Calibre +# Contributors & Acknowledgment - Start the Calibre DESIGNrev viewer in the temp directory and load your GDS file: -``` - calibredrv temp.gds -``` - Select Verification->Start RVE and select the results database file in - the new form (e.g., test1.drc.db). This will start the RVE (results - viewer). Scroll through the check pane and find the DRC check with an - error. Select it and it will open some numbers to the right. Double - click on any of the errors in the result browser. These will be - labelled as numbers "1 2 3 4" for example will be 4 DRC errors. +- [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. +- 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. - In the viewer ">" opens the layout down a level. +* * * -2. Glade +[Matthew Guthaus]: https://users.soe.ucsc.edu/~mrg +[James Stine]: https://ece.okstate.edu/content/stine-james-e-jr-phd +[VLSIDA]: https://vlsida.soe.ucsc.edu +[VLSIARCH]: https://vlsiarch.ecen.okstate.edu/ +[OpenRAMpaper]: https://ieeexplore.ieee.org/document/7827670/ - You can view errors in Glade as well. I like this because it is on my laptop. - You can get it from: http://www.peardrop.co.uk/glade/ +[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 - To remote display over X windows, you need to disable OpenGL acceleration or use vnc - or something. You can disable by adding this to your .bashrc in bash: -``` - export GLADE_USE_OPENGL=no -``` - or in .cshrc/.tcshrc in csh/tcsh: -``` - setenv GLADE_USE_OPENGAL no -``` - To use this with the FreePDK45 or SCMOS layer views you should use the - tech files. Then create a .glade.py file in your user directory with - these commands to load the technology layers: -``` -ui().importCds("default", -"/Users/mrg/techfiles/freepdk45/display.drf", -"/Users/mrg/techfiles/freepdk45/FreePDK45.tf", 1000, 1, -"/Users/mrg/techfiles/freepdk45/layers.map") -``` - Obviously, edit the paths to point to your directory. To switch - between processes, you have to change the importCds command (or you - can manually run the command each time you start glade). +[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 - To load the errors, you simply do Verify->Import Calibre Errors select - the .results file from Calibre. +[Magic]: http://opencircuitdesign.com/magic/ +[Netgen]: http://opencircuitdesign.com/netgen/ +[Qflow]: http://opencircuitdesign.com/qflow/history.html +[Ngspice]: http://ngspice.sourceforge.net/ -3. Magic - - Magic is only supported in SCMOS. You will need to install the MOSIS SCMOS rules - and Magic from: http://opencircuitdesign.com/ - - When running DRC or extraction, OpenRAM will load the GDS file, save - the .ext/.mag files, and export an extracted netlist (.spice). - -4. It is possible to use other viewers as well, such as: - * LayoutEditor http://www.layouteditor.net/ - - -# Example to output/input .gds layout files from/to Cadence - -1. To create your component layouts, you should stream them to - individual gds files using our provided layermap and flatten - cells. For example, -``` - strmout -layerMap layers.map -library sram -topCell $i -view layout -flattenVias -flattenPcells -strmFile ../gds_lib/$i.gds -``` -2. To stream a layout back into Cadence, do this: -``` - strmin -layerMap layers.map -attachTechFileOfLib NCSU_TechLib_FreePDK45 -library sram_4_32 -strmFile sram_4_32.gds -``` - When you import a gds file, make sure to attach the correct tech lib - or you will get incorrect layers in the resulting library. +[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 +[Slack]: https://join.slack.com/t/openram/shared_invite/enQtNDgxMjc3NzU5NTI1LTE4ODMyM2I0Mzk2ZmFiMjgwYTYyMTQ4NTgwMmUwMDhiM2E1MDViNDRjYzU1NjJhZTQxNWZjMzE3M2FlODBmZjA diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index 9eefc564..91e11280 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -70,40 +70,47 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): """Checks both DRC and LVS for a module""" # Unit tests will check themselves. # Do not run if disabled in options. - if not OPTS.is_unit_test and OPTS.check_lvsdrc: + + if (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)): + global total_drc_errors global total_lvs_errors tempspice = OPTS.openram_temp + "/temp.sp" tempgds = OPTS.openram_temp + "/temp.gds" self.sp_write(tempspice) self.gds_write(tempgds) - num_drc_errors = verify.run_drc(self.name, tempgds) - num_lvs_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification) + + num_drc_errors = verify.run_drc(self.name, tempgds, final_verification) + num_lvs_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification) debug.check(num_drc_errors == 0,"DRC failed for {0} with {1} error(s)".format(self.name,num_drc_errors)) debug.check(num_lvs_errors == 0,"LVS failed for {0} with {1} errors(s)".format(self.name,num_lvs_errors)) total_drc_errors += num_drc_errors total_lvs_errors += num_lvs_errors + os.remove(tempspice) os.remove(tempgds) - def DRC(self): + def DRC(self, final_verification=False): """Checks DRC for a module""" # Unit tests will check themselves. # Do not run if disabled in options. - if not OPTS.is_unit_test and OPTS.check_lvsdrc: + + if (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)): global total_drc_errors tempgds = OPTS.openram_temp + "/temp.gds" self.gds_write(tempgds) - num_errors = verify.run_drc(self.name, tempgds) + num_errors = verify.run_drc(self.name, tempgds, final_verification) total_drc_errors += num_errors debug.check(num_errors == 0,"DRC failed for {0} with {1} error(s)".format(self.name,num_error)) + os.remove(tempgds) def LVS(self, final_verification=False): """Checks LVS for a module""" # Unit tests will check themselves. # Do not run if disabled in options. - if not OPTS.is_unit_test and OPTS.check_lvsdrc: + + if (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)): global total_lvs_errors tempspice = OPTS.openram_temp + "/temp.sp" tempgds = OPTS.openram_temp + "/temp.gds" diff --git a/compiler/globals.py b/compiler/globals.py index 887b092e..3f2ff9ac 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -30,8 +30,10 @@ def parse_args(): help="Base output file name(s) prefix", metavar="FILE"), optparse.make_option("-p", "--outpath", dest="output_path", help="Output file(s) location"), + optparse.make_option("-i", "--inlinecheck", action="store_true", + help="Enable inline LVS/DRC checks", dest="inline_lvsdrc"), optparse.make_option("-n", "--nocheck", action="store_false", - help="Disable inline LVS/DRC checks", dest="check_lvsdrc"), + help="Disable all LVS/DRC checks", dest="check_lvsdrc"), optparse.make_option("-v", "--verbose", action="count", dest="debug_level", help="Increase the verbosity level"), optparse.make_option("-t", "--tech", dest="tech_name", @@ -388,10 +390,10 @@ def import_tech(): def print_time(name, now_time, last_time=None): """ Print a statement about the time delta. """ if last_time: - time = round((now_time-last_time).total_seconds(),1) + time = str(round((now_time-last_time).total_seconds(),1)) + " seconds" else: - time = now_time - print("** {0}: {1} seconds".format(name,time)) + time = now_time.strftime('%m/%d/%Y %H:%M:%S') + print("** {0}: {1}".format(name,time)) def report_status(): @@ -414,6 +416,9 @@ def report_status(): if OPTS.netlist_only: print("Netlist only mode (no physical design is being done).") + if not OPTS.inline_lvsdrc: + print("DRC/LVS/PEX is only run on the top-level design.") + if not OPTS.check_lvsdrc: - print("DRC/LVS/PEX checking is disabled.") - + print("DRC/LVS/PEX is completely disabled.") + diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index cd1b83e4..f2226797 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -19,6 +19,7 @@ class bank(design.design): Dynamically generated a single bank including bitcell array, hierarchical_decoder, precharge, (optional column_mux and column decoder), write driver and sense amplifiers. + This can create up to two ports in any combination: rw, w, r. """ def __init__(self, sram_config, name=""): @@ -30,7 +31,6 @@ class bank(design.design): design.design.__init__(self, name) debug.info(2, "create sram of size {0} with {1} words".format(self.word_size,self.num_words)) - # The local control signals are gated when we have bank select logic, # so this prefix will be added to all of the input signals to create # the internal gated signals. @@ -41,6 +41,7 @@ class bank(design.design): self.create_netlist() if not OPTS.netlist_only: + debug.check(len(self.all_ports)<=2,"Bank layout cannot handle more than two ports.") self.create_layout() @@ -48,11 +49,11 @@ class bank(design.design): self.compute_sizes() self.add_pins() self.add_modules() - self.create_modules() + self.create_instances() def create_layout(self): - self.place_modules() + self.place_instances() self.setup_routing_constraints() self.route_layout() @@ -96,44 +97,107 @@ class bank(design.design): def route_layout(self): """ Create routing amoung the modules """ self.route_central_bus() - self.route_precharge_to_bitcell_array() - self.route_col_mux_to_precharge_array() - self.route_sense_amp_to_col_mux_or_precharge_array() - self.route_write_driver_to_sense_amp() - self.route_sense_amp_out() - self.route_wordline_driver() - self.route_write_driver() - self.route_row_decoder() - self.route_column_address_lines() - self.route_control_lines() - if self.num_banks > 1: - self.route_bank_select() + + for port in self.all_ports: + self.route_bitlines(port) + self.route_wordline_driver(port) + self.route_row_decoder(port) + self.route_column_address_lines(port) + self.route_control_lines(port) + if self.num_banks > 1: + self.route_bank_select(port) self.route_supplies() - - def create_modules(self): - """ Add modules. The order should not matter! """ - # Above the bitcell array - self.create_bitcell_array() - self.create_precharge_array() + def route_bitlines(self, port): + """ Route the bitlines depending on the port type rw, w, or r. """ - # Below the bitcell array + if port in self.readwrite_ports: + # write_driver -> sense_amp -> (column_mux) -> precharge -> bitcell_array + self.route_write_driver_in(port) + self.route_sense_amp_out(port) + self.route_write_driver_to_sense_amp(port) + self.route_sense_amp_to_column_mux_or_precharge_array(port) + self.route_column_mux_to_precharge_array(port) + self.route_precharge_to_bitcell_array(port) + elif port in self.read_ports: + # sense_amp -> (column_mux) -> precharge -> bitcell_array + self.route_sense_amp_out(port) + self.route_sense_amp_to_column_mux_or_precharge_array(port) + self.route_column_mux_to_precharge_array(port) + self.route_precharge_to_bitcell_array(port) + else: + # write_driver -> (column_mux) -> bitcell_array + self.route_write_driver_in(port) + self.route_write_driver_to_column_mux_or_bitcell_array(port) + self.route_column_mux_to_bitcell_array(port) + + def create_instances(self): + """ Create the instances of the netlist. """ + + self.create_bitcell_array() + + self.create_precharge_array() self.create_column_mux_array() self.create_sense_amp_array() self.create_write_driver_array() - # To the left of the bitcell array self.create_row_decoder() self.create_wordline_driver() self.create_column_decoder() self.create_bank_select() - def compute_module_offsets(self): + def compute_instance_offsets(self): """ - Compute the module offsets. + Compute the empty instance offsets for port0 and port1 (if needed) """ + + # These are created even if the port type (e.g. read only) + # doesn't need the instance (e.g. write driver). + + # Create the bottom-up and left to right order of components in each port + # which deepends on the port type rw, w, r + self.vertical_port_order = [] + self.vertical_port_offsets = [] + for port in self.all_ports: + self.vertical_port_order.append([]) + self.vertical_port_offsets.append([None]*4) + + # For later placement, these are fixed in the order: write driver, + # sense amp, clumn mux, precharge, even if the item is not used + # in a given port (it will be None then) + self.vertical_port_order[port].append(self.write_driver_array_inst[port]) + self.vertical_port_order[port].append(self.sense_amp_array_inst[port]) + self.vertical_port_order[port].append(self.column_mux_array_inst[port]) + self.vertical_port_order[port].append(self.precharge_array_inst[port]) + + # For the odd ones they will go on top, so reverse in place + if port%2: + self.vertical_port_order[port]=self.vertical_port_order[port][::-1] + + self.write_driver_offsets = [None]*len(self.all_ports) + self.sense_amp_offsets = [None]*len(self.all_ports) + self.column_mux_offsets = [None]*len(self.all_ports) + self.precharge_offsets = [None]*len(self.all_ports) + + self.wordline_driver_offsets = [None]*len(self.all_ports) + self.row_decoder_offsets = [None]*len(self.all_ports) + + self.column_decoder_offsets = [None]*len(self.all_ports) + self.bank_select_offsets = [None]*len(self.all_ports) + + self.compute_instance_port0_offsets() + if len(self.all_ports)==2: + self.compute_instance_port1_offsets() + + + def compute_instance_port0_offsets(self): + """ + Compute the instance offsets for port0. + """ + + port = 0 # UPPER RIGHT QUADRANT # Bitcell array is placed at (0,0) @@ -141,64 +205,127 @@ class bank(design.design): # LOWER RIGHT QUADRANT # Below the bitcell array - y_offset = self.precharge_array[0].height + self.m2_gap - self.precharge_offset = vector(0,-y_offset) - if self.col_addr_size > 0: - y_offset += self.column_mux_array[0].height + self.m2_gap - self.column_mux_offset = vector(0,-y_offset) - y_offset += self.sense_amp_array.height + self.m2_gap - self.sense_amp_offset = vector(0,-y_offset) - y_offset += self.write_driver_array.height + self.m2_gap - self.write_driver_offset = vector(0,-y_offset) + y_height = 0 + for p in self.vertical_port_order[port]: + if p==None: + continue + y_height += p.height + self.m2_gap + + y_offset = -y_height + for i,p in enumerate(self.vertical_port_order[port]): + if p==None: + continue + self.vertical_port_offsets[port][i]=vector(0,y_offset) + y_offset += (p.height + self.m2_gap) + + self.write_driver_offsets[port] = self.vertical_port_offsets[port][0] + self.sense_amp_offsets[port] = self.vertical_port_offsets[port][1] + self.column_mux_offsets[port] = self.vertical_port_offsets[port][2] + self.precharge_offsets[port] = self.vertical_port_offsets[port][3] # UPPER LEFT QUADRANT # To the left of the bitcell array # The wordline driver is placed to the right of the main decoder width. - x_offset = self.central_bus_width + self.wordline_driver.width - self.m2_pitch - self.wordline_driver_offset = vector(-x_offset,0) - x_offset += self.row_decoder.width + self.m2_pitch - self.row_decoder_offset = vector(-x_offset,0) + x_offset = self.central_bus_width + self.wordline_driver.width + self.wordline_driver_offsets[port] = vector(-x_offset,0) + x_offset += self.row_decoder.width + self.m2_gap + 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 if self.col_addr_size > 0: - y_offset = self.col_decoder.height + y_offset = self.column_decoder.height else: y_offset = 0 y_offset += 2*drc("well_to_well") - self.column_decoder_offset = vector(-x_offset,-y_offset) + self.column_decoder_offsets[port] = vector(-x_offset,-y_offset) # Bank select gets placed below the column decoder (x_offset doesn't change) if self.col_addr_size > 0: - y_offset = min(self.column_decoder_offset.y, self.column_mux_offset.y) + y_offset = min(self.column_decoder_offsets[port].y, self.column_mux_offsets[port].y) else: - y_offset = self.row_decoder_offset.y + y_offset = self.row_decoder_offsets[port].y if self.num_banks > 1: y_offset += self.bank_select.height + drc("well_to_well") - self.bank_select_offset = vector(-x_offset,-y_offset) - - def place_modules(self): - """ Place the modules. """ + self.bank_select_offsets[port] = vector(-x_offset,-y_offset) - self.compute_module_offsets() + def compute_instance_port1_offsets(self): + """ + Compute the instance offsets for port1 on the top of the bank. + """ + + port=1 + + # The center point for these cells are the upper-right corner of + # the bitcell array. + # The decoder/driver logic is placed on the right and mirrored on Y-axis. + # The write/sense/precharge/mux is placed on the top and mirrored on the X-axis. + + # LOWER LEFT QUADRANT + # Bitcell array is placed at (0,0) + + # UPPER LEFT QUADRANT + # Above the bitcell array + y_offset = self.bitcell_array.height + self.m2_gap + for i,p in enumerate(self.vertical_port_order[port]): + if p==None: + continue + y_offset += (p.height + self.m2_gap) + self.vertical_port_offsets[port][i]=vector(0,y_offset) + + # Reversed order + self.write_driver_offsets[port] = self.vertical_port_offsets[port][3] + self.sense_amp_offsets[port] = self.vertical_port_offsets[port][2] + self.column_mux_offsets[port] = self.vertical_port_offsets[port][1] + self.precharge_offsets[port] = self.vertical_port_offsets[port][0] + + # LOWER RIGHT QUADRANT + # To the left of the bitcell array + # The wordline driver is placed to the right of the main decoder width. + x_offset = self.bitcell_array.width + self.central_bus_width + self.wordline_driver.width + self.wordline_driver_offsets[port] = vector(x_offset,0) + x_offset += self.row_decoder.width + self.m2_gap + self.row_decoder_offsets[port] = vector(x_offset,0) + + # 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 + y_offset += 2*drc("well_to_well") + self.column_decoder_offsets[port] = vector(x_offset,y_offset) + + # Bank select gets placed above the column decoder (x_offset doesn't change) + if self.col_addr_size > 0: + y_offset = max(self.column_decoder_offsets[port].y + self.column_decoder.height, + self.column_mux_offsets[port].y + self.column_mux_array[port].height) + else: + y_offset = self.row_decoder_offsets[port].y + self.bank_select_offsets[port] = vector(x_offset,y_offset) + + def place_instances(self): + """ Place the instances. """ + + self.compute_instance_offsets() # UPPER RIGHT QUADRANT self.place_bitcell_array(self.bitcell_array_offset) # LOWER RIGHT QUADRANT - self.place_precharge_array([self.precharge_offset]*len(self.read_ports)) - self.place_column_mux_array([self.column_mux_offset]*len(self.all_ports)) - self.place_sense_amp_array([self.sense_amp_offset]*len(self.read_ports)) - self.place_write_driver_array([self.write_driver_offset]*len(self.write_ports)) + # These are fixed in the order: write driver, sense amp, clumn mux, precharge, + # even if the item is not used in a given port (it will be None then) + self.place_write_driver_array(self.write_driver_offsets) + self.place_sense_amp_array(self.sense_amp_offsets) + self.place_column_mux_array(self.column_mux_offsets) + self.place_precharge_array(self.precharge_offsets) # UPPER LEFT QUADRANT - self.place_row_decoder([self.row_decoder_offset]*len(self.all_ports)) - self.place_wordline_driver([self.wordline_driver_offset]*len(self.all_ports)) + self.place_row_decoder(self.row_decoder_offsets) + self.place_wordline_driver(self.wordline_driver_offsets) # LOWER LEFT QUADRANT - self.place_column_decoder([self.column_decoder_offset]*len(self.all_ports)) - self.place_bank_select([self.bank_select_offset]*len(self.all_ports)) + self.place_column_decoder(self.column_decoder_offsets) + self.place_bank_select(self.bank_select_offsets) def compute_sizes(self): @@ -249,7 +376,7 @@ class bank(design.design): # The width of this bus is needed to place other modules (e.g. decoder) # A width on each side too - self.central_bus_width = self.m2_pitch * self.num_control_lines + 2*self.m2_width + self.central_bus_width = self.m2_pitch * self.num_control_lines + self.m2_width # A space for wells or jogging m2 self.m2_gap = max(2*drc("pwell_to_nwell") + drc("well_enclosure_active"), @@ -257,7 +384,7 @@ class bank(design.design): def add_modules(self): - """ Create all the modules using the class loader """ + """ Add all the modules using the class loader """ mod_list = ["bitcell", "decoder", "wordline_driver", "bitcell_array", "sense_amp_array", "precharge_array", @@ -351,10 +478,10 @@ class bank(design.design): def create_precharge_array(self): """ Creating Precharge """ - self.precharge_array_inst = [] + self.precharge_array_inst = [None]*len(self.all_ports) for port in self.read_ports: - self.precharge_array_inst.append(self.add_inst(name="precharge_array{}".format(port), - mod=self.precharge_array[port])) + self.precharge_array_inst[port]=self.add_inst(name="precharge_array{}".format(port), + mod=self.precharge_array[port]) temp = [] for i in range(self.num_cols): temp.append(self.bl_names[port]+"_{0}".format(i)) @@ -368,21 +495,24 @@ class bank(design.design): debug.check(len(offsets)>=len(self.all_ports), "Insufficient offsets to place precharge array.") - # FIXME: place for multiport for port in self.read_ports: - self.precharge_array_inst[port].place(offsets[port]) + if port%2 == 1: + mirror = "MX" + else: + mirror = "R0" + self.precharge_array_inst[port].place(offset=offsets[port], mirror=mirror) def create_column_mux_array(self): """ Creating Column Mux when words_per_row > 1 . """ - self.col_mux_array_inst = [] + self.column_mux_array_inst = [None]*len(self.all_ports) if self.col_addr_size == 0: return for port in self.all_ports: - self.col_mux_array_inst.append(self.add_inst(name="column_mux_array{}".format(port), - mod=self.column_mux_array[port])) + self.column_mux_array_inst[port] = self.add_inst(name="column_mux_array{}".format(port), + mod=self.column_mux_array[port]) temp = [] for col in range(self.num_cols): @@ -406,16 +536,20 @@ class bank(design.design): debug.check(len(offsets)>=len(self.all_ports), "Insufficient offsets to place column mux array.") for port in self.all_ports: - self.col_mux_array_inst[port].place(offsets[port]) + if port%2 == 1: + mirror = "MX" + else: + mirror = "R0" + self.column_mux_array_inst[port].place(offset=offsets[port], mirror=mirror) def create_sense_amp_array(self): """ Creating Sense amp """ - self.sense_amp_array_inst = [] + self.sense_amp_array_inst = [None]*len(self.all_ports) for port in self.read_ports: - self.sense_amp_array_inst.append(self.add_inst(name="sense_amp_array{}".format(port), - mod=self.sense_amp_array)) + self.sense_amp_array_inst[port] = self.add_inst(name="sense_amp_array{}".format(port), + mod=self.sense_amp_array) temp = [] for bit in range(self.word_size): @@ -435,23 +569,21 @@ class bank(design.design): """ Placing Sense amp """ debug.check(len(offsets)>=len(self.read_ports), "Insufficient offsets to place sense amp array.") - - # FIXME: place for multiport for port in self.read_ports: - self.sense_amp_array_inst[port].place(offsets[port]) + if port%2 == 1: + mirror = "MX" + else: + mirror = "R0" + self.sense_amp_array_inst[port].place(offset=offsets[port], mirror=mirror) def create_write_driver_array(self): """ Creating Write Driver """ - self.write_driver_array_inst = [] - for port in self.all_ports: - if port in self.write_ports: - self.write_driver_array_inst.append(self.add_inst(name="write_driver_array{}".format(port), - mod=self.write_driver_array)) - else: - self.write_driver_array_inst.append(None) - continue + self.write_driver_array_inst = [None]*len(self.all_ports) + for port in self.write_ports: + self.write_driver_array_inst[port] = self.add_inst(name="write_driver_array{}".format(port), + mod=self.write_driver_array) temp = [] for bit in range(self.word_size): @@ -473,16 +605,20 @@ class bank(design.design): debug.check(len(offsets)>=len(self.write_ports), "Insufficient offsets to place write driver array.") for port in self.write_ports: - self.write_driver_array_inst[port].place(offsets[port]) + if port%2 == 1: + mirror = "MX" + else: + mirror = "R0" + self.write_driver_array_inst[port].place(offset=offsets[port], mirror=mirror) def create_row_decoder(self): """ Create the hierarchical row decoder """ - self.row_decoder_inst = [] + self.row_decoder_inst = [None]*len(self.all_ports) for port in self.all_ports: - self.row_decoder_inst.append(self.add_inst(name="row_decoder{}".format(port), - mod=self.row_decoder)) + self.row_decoder_inst[port] = self.add_inst(name="row_decoder{}".format(port), + mod=self.row_decoder) temp = [] for bit in range(self.row_addr_size): @@ -504,18 +640,21 @@ class bank(design.design): # The predecoder is below the x-axis and the main decoder is above the x-axis # The address flop and decoder are aligned in the x coord. - # FIXME: place for multiport for port in self.all_ports: - self.row_decoder_inst[port].place(offsets[port]) + if port%2 == 1: + mirror = "MY" + else: + mirror = "R0" + self.row_decoder_inst[port].place(offset=offsets[port], mirror=mirror) def create_wordline_driver(self): """ Create the Wordline Driver """ - self.wordline_driver_inst = [] + self.wordline_driver_inst = [None]*len(self.all_ports) for port in self.all_ports: - self.wordline_driver_inst.append(self.add_inst(name="wordline_driver{}".format(port), - mod=self.wordline_driver)) + self.wordline_driver_inst[port] = self.add_inst(name="wordline_driver{}".format(port), + mod=self.wordline_driver) temp = [] for row in range(self.num_rows): @@ -534,7 +673,11 @@ class bank(design.design): debug.check(len(offsets)>=len(self.all_ports), "Insufficient offsets to place wordline driver array.") for port in self.all_ports: - self.wordline_driver_inst[port].place(offsets[port]) + if port%2 == 1: + mirror = "MY" + else: + mirror = "R0" + self.wordline_driver_inst[port].place(offset=offsets[port], mirror=mirror) def create_column_decoder(self): @@ -545,20 +688,20 @@ class bank(design.design): if self.col_addr_size == 0: return elif self.col_addr_size == 1: - self.col_decoder = pinvbuf(height=self.mod_dff.height) - self.add_mod(self.col_decoder) + self.column_decoder = pinvbuf(height=self.mod_dff.height) + self.add_mod(self.column_decoder) elif self.col_addr_size == 2: - self.col_decoder = self.row_decoder.pre2_4 + self.column_decoder = self.row_decoder.pre2_4 elif self.col_addr_size == 3: - self.col_decoder = self.row_decoder.pre3_8 + self.column_decoder = self.row_decoder.pre3_8 else: # No error checking before? debug.error("Invalid column decoder?",-1) - self.col_decoder_inst = [] + self.column_decoder_inst = [None]*len(self.all_ports) for port in self.all_ports: - self.col_decoder_inst.append(self.add_inst(name="col_address_decoder{}".format(port), - mod=self.col_decoder)) + self.column_decoder_inst[port] = self.add_inst(name="col_address_decoder{}".format(port), + mod=self.column_decoder) temp = [] for bit in range(self.col_addr_size): @@ -579,7 +722,11 @@ class bank(design.design): debug.check(len(offsets)>=len(self.all_ports), "Insufficient offsets to place column decoder.") for port in self.all_ports: - self.col_decoder_inst[port].place(offsets[port]) + if port%2 == 1: + mirror = "MY" + else: + mirror = "R0" + self.column_decoder_inst[port].place(offset=offsets[port], mirror=mirror) @@ -589,10 +736,10 @@ class bank(design.design): if not self.num_banks > 1: return - self.bank_select_inst = [] + self.bank_select_inst = [None]*len(self.all_ports) for port in self.all_ports: - self.bank_select_inst.append(self.add_inst(name="bank_select{}".format(port), - mod=self.bank_select)) + self.bank_select_inst[port] = self.add_inst(name="bank_select{}".format(port), + mod=self.bank_select) temp = [] temp.extend(self.input_control_signals[port]) @@ -620,39 +767,38 @@ class bank(design.design): self.copy_power_pins(inst,"vdd") self.copy_power_pins(inst,"gnd") - def route_bank_select(self): + def route_bank_select(self, port): """ Route the bank select logic. """ - for port in self.all_ports: - if self.port_id[port] == "rw": - bank_sel_signals = ["clk_buf", "clk_buf_bar", "w_en", "s_en", "bank_sel"] - gated_bank_sel_signals = ["gated_clk_buf", "gated_clk_buf_bar", "gated_w_en", "gated_s_en"] - elif self.port_id[port] == "w": - bank_sel_signals = ["clk_buf", "clk_buf_bar", "w_en", "bank_sel"] - gated_bank_sel_signals = ["gated_clk_buf", "gated_clk_buf_bar", "gated_w_en"] - else: - bank_sel_signals = ["clk_buf", "clk_buf_bar", "s_en", "bank_sel"] - gated_bank_sel_signals = ["gated_clk_buf", "gated_clk_buf_bar", "gated_s_en"] + if self.port_id[port] == "rw": + bank_sel_signals = ["clk_buf", "clk_buf_bar", "w_en", "s_en", "bank_sel"] + gated_bank_sel_signals = ["gated_clk_buf", "gated_clk_buf_bar", "gated_w_en", "gated_s_en"] + elif self.port_id[port] == "w": + bank_sel_signals = ["clk_buf", "clk_buf_bar", "w_en", "bank_sel"] + gated_bank_sel_signals = ["gated_clk_buf", "gated_clk_buf_bar", "gated_w_en"] + else: + bank_sel_signals = ["clk_buf", "clk_buf_bar", "s_en", "bank_sel"] + gated_bank_sel_signals = ["gated_clk_buf", "gated_clk_buf_bar", "gated_s_en"] - copy_control_signals = self.input_control_signals[port]+["bank_sel{}".format(port)] - for signal in range(len(copy_control_signals)): - self.copy_layout_pin(self.bank_select_inst[port], bank_sel_signals[signal], copy_control_signals[signal]) + copy_control_signals = self.input_control_signals[port]+["bank_sel{}".format(port)] + for signal in range(len(copy_control_signals)): + self.copy_layout_pin(self.bank_select_inst[port], bank_sel_signals[signal], copy_control_signals[signal]) - for signal in range(len(gated_bank_sel_signals)): - # Connect the inverter output to the central bus - out_pos = self.bank_select_inst[port].get_pin(gated_bank_sel_signals[signal]).rc() - name = self.control_signals[port][signal] - bus_pos = vector(self.bus_xoffset[name].x, out_pos.y) - self.add_path("metal3",[out_pos, bus_pos]) - self.add_via_center(layers=("metal2", "via2", "metal3"), - offset=bus_pos, - rotate=90) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=out_pos, - rotate=90) - self.add_via_center(layers=("metal2", "via2", "metal3"), - offset=out_pos, - rotate=90) + for signal in range(len(gated_bank_sel_signals)): + # Connect the inverter output to the central bus + out_pos = self.bank_select_inst[port].get_pin(gated_bank_sel_signals[signal]).rc() + name = self.control_signals[port][signal] + bus_pos = vector(self.bus_xoffset[port][name].x, out_pos.y) + self.add_path("metal3",[out_pos, bus_pos]) + self.add_via_center(layers=("metal2", "via2", "metal3"), + offset=bus_pos, + rotate=90) + self.add_via_center(layers=("metal1", "via1", "metal2"), + offset=out_pos, + rotate=90) + self.add_via_center(layers=("metal2", "via2", "metal3"), + offset=out_pos, + rotate=90) def setup_routing_constraints(self): @@ -660,32 +806,12 @@ class bank(design.design): After the modules are instantiated, find the dimensions for the control bus, power ring, etc. """ - # FIXME: calculate for multiport - - #The minimum point is either the bottom of the address flops, - #the column decoder (if there is one). - write_driver_min_y_offset = self.write_driver_array_inst[0].by() - 3*self.m2_pitch - row_decoder_min_y_offset = self.row_decoder_inst[0].by() - - if self.col_addr_size > 0: - col_decoder_min_y_offset = self.col_decoder_inst[0].by() - else: - col_decoder_min_y_offset = row_decoder_min_y_offset - - if self.num_banks>1: - # The control gating logic is below the decoder - # Min of the control gating logic and write driver. - self.min_y_offset = min(col_decoder_min_y_offset - self.bank_select.height, write_driver_min_y_offset) - else: - # Just the min of the decoder logic logic and write driver. - self.min_y_offset = min(col_decoder_min_y_offset, write_driver_min_y_offset) - # The max point is always the top of the precharge bitlines - # Add a vdd and gnd power rail above the array - # FIXME: Update multiport - self.max_y_offset = self.bitcell_array_inst.ur().y + 3*self.m1_width - self.max_x_offset = self.bitcell_array_inst.ur().x + 3*self.m1_width - self.min_x_offset = self.row_decoder_inst[0].lx() + self.max_y_offset = max([x.uy() for x in self.insts]) + 3*self.m1_width + self.min_y_offset = min([x.by() for x in self.insts]) + + self.max_x_offset = max([x.rx() for x in self.insts]) + 3*self.m1_width + self.min_x_offset = min([x.lx() for x in self.insts]) # # Create the core bbox for the power rings ur = vector(self.max_x_offset, self.max_y_offset) @@ -701,126 +827,163 @@ class bank(design.design): # Overall central bus width. It includes all the column mux lines, # and control lines. + + self.bus_xoffset = [None]*len(self.all_ports) + # Port 0 # The bank is at (0,0), so this is to the left of the y-axis. # 2 pitches on the right for vias/jogs to access the inputs - for port in self.all_ports: - control_bus_offset = vector(-self.m2_pitch * self.num_control_lines - self.m2_width, self.min_y_offset) - control_bus_length = self.max_y_offset - self.min_y_offset - self.bus_xoffset = self.create_bus(layer="metal2", - pitch=self.m2_pitch, - offset=control_bus_offset, - names=self.control_signals[port], - length=control_bus_length, - vertical=True, - make_pins=(self.num_banks==1)) + control_bus_offset = vector(-self.m2_pitch * self.num_control_lines - self.m2_width, self.min_y_offset) + control_bus_length = self.max_y_offset - self.min_y_offset + self.bus_xoffset[0] = self.create_bus(layer="metal2", + pitch=self.m2_pitch, + offset=control_bus_offset, + names=self.control_signals[0], + length=control_bus_length, + vertical=True, + make_pins=(self.num_banks==1)) + + # Port 1 + if len(self.all_ports)==2: + control_bus_offset = vector(self.bitcell_array.width + self.m2_width, self.min_y_offset) + self.bus_xoffset[1] = self.create_bus(layer="metal2", + pitch=self.m2_pitch, + offset=control_bus_offset, + names=self.control_signals[1], + length=control_bus_length, + vertical=True, + make_pins=(self.num_banks==1)) + - - def route_precharge_to_bitcell_array(self): + def route_precharge_to_bitcell_array(self, port): """ Routing of BL and BR between pre-charge and bitcell array """ - # FIXME: Update for multiport - for port in self.read_ports: - for col in range(self.num_cols): - precharge_bl = self.precharge_array_inst[port].get_pin("bl_{}".format(col)).uc() - precharge_br = self.precharge_array_inst[port].get_pin("br_{}".format(col)).uc() - bitcell_bl = self.bitcell_array_inst.get_pin(self.bl_names[port]+"_{}".format(col)).bc() - bitcell_br = self.bitcell_array_inst.get_pin(self.br_names[port]+"_{}".format(col)).bc() + inst2 = self.precharge_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) + + - yoffset = 0.5*(precharge_bl.y+bitcell_bl.y) - self.add_path("metal2",[precharge_bl, vector(precharge_bl.x,yoffset), - vector(bitcell_bl.x,yoffset), bitcell_bl]) - self.add_path("metal2",[precharge_br, vector(precharge_br.x,yoffset), - vector(bitcell_br.x,yoffset), bitcell_br]) - - - def route_col_mux_to_precharge_array(self): + def route_column_mux_to_precharge_array(self, port): """ Routing of BL and BR between col mux and precharge array """ # Only do this if we have a column mux! if self.col_addr_size==0: return - # FIXME: Update for multiport - for port in self.all_ports: - bottom_inst = self.col_mux_array_inst[port] - top_inst = self.precharge_array_inst[port] - self.connect_bitlines(top_inst, bottom_inst, self.num_cols) + 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) + def route_column_mux_to_bitcell_array(self, port): + """ Routing of BL and BR between col mux bitcell array """ + + # Only do this if we have a column mux! + 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) + - def route_sense_amp_to_col_mux_or_precharge_array(self): + 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 """ - - for port in self.read_ports: - bottom_inst = self.sense_amp_array_inst[port] + bottom_inst = 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_{}" + else: + # Sense amp is directly connected to the precharge array + top_inst = self.precharge_array_inst[port] + top_bl = "bl_{}" + top_br = "br_{}" - if self.col_addr_size>0: - # Sense amp is connected to the col mux - top_inst = self.col_mux_array_inst[port] - top_bl = "bl_out_{}" - top_br = "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_{}" - - self.connect_bitlines(top_inst, bottom_inst, self.word_size, - top_bl_name=top_bl, top_br_name=top_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) - def route_write_driver_to_sense_amp(self): + 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] + + 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_{}" + else: + # Sense amp is directly connected to the precharge array + top_inst = self.precharge_array_inst[port] + top_bl = "bl_{}" + top_br = "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) + + def route_write_driver_to_sense_amp(self, port): """ Routing of BL and BR between write driver and sense amp """ - - for port in self.write_ports: - bottom_inst = self.write_driver_array_inst[port] - top_inst = self.sense_amp_array_inst[port] - self.connect_bitlines(top_inst, bottom_inst, self.word_size) - + + inst1 = self.write_driver_array_inst[port] + inst2 = self.sense_amp_array_inst[port] + self.connect_bitlines(inst1, inst2, self.word_size) - def route_sense_amp_out(self): + def route_sense_amp_out(self, port): """ Add pins for the sense amp output """ - - # FIXME: Update for multiport - for port in self.read_ports: - 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), - layer=data_pin.layer, - offset=data_pin.center(), - height=data_pin.height(), - width=data_pin.width()) + + 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), + layer=data_pin.layer, + offset=data_pin.center(), + height=data_pin.height(), + width=data_pin.width()) - def route_row_decoder(self): + def route_row_decoder(self, port): """ Routes the row decoder inputs and supplies """ - # FIXME: Update for multiport # Create inputs for the row address lines - for port in self.all_ports: - for row in range(self.row_addr_size): - addr_idx = row + self.col_addr_size - decoder_name = "addr_{}".format(row) - addr_name = "addr{0}_{1}".format(port,addr_idx) - self.copy_layout_pin(self.row_decoder_inst[port], decoder_name, addr_name) + for row in range(self.row_addr_size): + addr_idx = row + self.col_addr_size + decoder_name = "addr_{}".format(row) + addr_name = "addr{0}_{1}".format(port,addr_idx) + self.copy_layout_pin(self.row_decoder_inst[port], decoder_name, addr_name) - def route_write_driver(self): + def route_write_driver_in(self, port): """ Connecting write driver """ - for port in self.all_ports: - for row in range(self.word_size): - data_name = "data_{}".format(row) - din_name = "din{0}_{1}".format(port,row) - self.copy_layout_pin(self.write_driver_array_inst[port], data_name, din_name) - def connect_bitlines(self, top_inst, bottom_inst, num_items, - top_bl_name="bl_{}", top_br_name="br_{}", bottom_bl_name="bl_{}", bottom_br_name="br_{}"): + for row in range(self.word_size): + data_name = "data_{}".format(row) + din_name = "din{0}_{1}".format(port,row) + self.copy_layout_pin(self.write_driver_array_inst[port], data_name, din_name) + + def connect_bitlines(self, inst1, inst2, num_bits, + inst1_bl_name="bl_{}", inst1_br_name="br_{}", + inst2_bl_name="bl_{}", inst2_br_name="br_{}"): """ Connect the bl and br of two modules. This assumes that they have sufficient space to create a jog - in the middle between the two modules (if needed) + in the middle between the two modules (if needed). """ - for col in range(num_items): + + # 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) + + for col in range(num_bits): bottom_bl = bottom_inst.get_pin(bottom_bl_name.format(col)).uc() bottom_br = bottom_inst.get_pin(bottom_br_name.format(col)).uc() top_bl = top_inst.get_pin(top_bl_name.format(col)).bc() @@ -832,61 +995,125 @@ class bank(design.design): self.add_path("metal2",[bottom_br, vector(bottom_br.x,yoffset), vector(top_br.x,yoffset), top_br]) - - def route_wordline_driver(self): + + def route_wordline_driver(self, port): + """ Connect Wordline driver to bitcell array wordline """ + if port%2: + self.route_wordline_driver_right(port) + else: + self.route_wordline_driver_left(port) + + def route_wordline_driver_left(self, port): """ Connecting Wordline driver output to Bitcell WL connection """ - for port in self.all_ports: - for row in range(self.num_rows): - # The pre/post is to access the pin from "outside" the cell to avoid DRCs - decoder_out_pos = self.row_decoder_inst[port].get_pin("decode_{}".format(row)).rc() - driver_in_pos = self.wordline_driver_inst[port].get_pin("in_{}".format(row)).lc() - mid1 = decoder_out_pos.scale(0.5,1)+driver_in_pos.scale(0.5,0) - mid2 = decoder_out_pos.scale(0.5,0)+driver_in_pos.scale(0.5,1) - self.add_path("metal1", [decoder_out_pos, mid1, mid2, driver_in_pos]) - # The mid guarantees we exit the input cell to the right. - driver_wl_pos = self.wordline_driver_inst[port].get_pin("wl_{}".format(row)).rc() - bitcell_wl_pos = self.bitcell_array_inst.get_pin(self.wl_names[port]+"_{}".format(row)).lc() - mid1 = driver_wl_pos.scale(0.5,1)+bitcell_wl_pos.scale(0.5,0) - mid2 = driver_wl_pos.scale(0.5,0)+bitcell_wl_pos.scale(0.5,1) - self.add_path("metal1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) - + for row in range(self.num_rows): + # The pre/post is to access the pin from "outside" the cell to avoid DRCs + decoder_out_pos = self.row_decoder_inst[port].get_pin("decode_{}".format(row)).rc() + driver_in_pos = self.wordline_driver_inst[port].get_pin("in_{}".format(row)).lc() + mid1 = decoder_out_pos.scale(0.5,1)+driver_in_pos.scale(0.5,0) + mid2 = decoder_out_pos.scale(0.5,0)+driver_in_pos.scale(0.5,1) + self.add_path("metal1", [decoder_out_pos, mid1, mid2, driver_in_pos]) - def route_column_address_lines(self): + # The mid guarantees we exit the input cell to the right. + driver_wl_pos = self.wordline_driver_inst[port].get_pin("wl_{}".format(row)).rc() + bitcell_wl_pos = self.bitcell_array_inst.get_pin(self.wl_names[port]+"_{}".format(row)).lc() + mid1 = driver_wl_pos.scale(0.5,1)+bitcell_wl_pos.scale(0.5,0) + mid2 = driver_wl_pos.scale(0.5,0)+bitcell_wl_pos.scale(0.5,1) + self.add_path("metal1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) + + + def route_wordline_driver_right(self, port): + """ Connecting Wordline driver output to Bitcell WL connection """ + + for row in range(self.num_rows): + # The pre/post is to access the pin from "outside" the cell to avoid DRCs + decoder_out_pos = self.row_decoder_inst[port].get_pin("decode_{}".format(row)).lc() + driver_in_pos = self.wordline_driver_inst[port].get_pin("in_{}".format(row)).rc() + mid1 = decoder_out_pos.scale(0.5,1)+driver_in_pos.scale(0.5,0) + mid2 = decoder_out_pos.scale(0.5,0)+driver_in_pos.scale(0.5,1) + self.add_path("metal1", [decoder_out_pos, mid1, mid2, driver_in_pos]) + + # The mid guarantees we exit the input cell to the right. + driver_wl_pos = self.wordline_driver_inst[port].get_pin("wl_{}".format(row)).lc() + bitcell_wl_pos = self.bitcell_array_inst.get_pin(self.wl_names[port]+"_{}".format(row)).rc() + mid1 = driver_wl_pos.scale(0.5,1)+bitcell_wl_pos.scale(0.5,0) + mid2 = driver_wl_pos.scale(0.5,0)+bitcell_wl_pos.scale(0.5,1) + self.add_path("metal1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) + + def route_column_address_lines(self, port): + if port%2: + self.route_column_address_lines_right(port) + else: + self.route_column_address_lines_left(port) + + def route_column_address_lines_left(self, port): """ Connecting the select lines of column mux to the address bus """ if not self.col_addr_size>0: return - for port in self.all_ports: - if self.col_addr_size == 1: + if self.col_addr_size == 1: + + # Connect to sel[0] and sel[1] + decode_names = ["Zb", "Z"] + + # The Address LSB + self.copy_layout_pin(self.column_decoder_inst[port], "A", "addr{}_0".format(port)) - # Connect to sel[0] and sel[1] - decode_names = ["Zb", "Z"] + elif self.col_addr_size > 1: + decode_names = [] + for i in range(self.num_col_addr_lines): + decode_names.append("out_{}".format(i)) + + for i in range(self.col_addr_size): + decoder_name = "in_{}".format(i) + addr_name = "addr{0}_{1}".format(port,i) + self.copy_layout_pin(self.column_decoder_inst[port], decoder_name, addr_name) + + offset = self.column_decoder_inst[port].lr() + vector(self.m2_pitch, 0) + + sel_names = ["sel_{}".format(x) for x in range(self.num_col_addr_lines)] + + route_map = list(zip(decode_names, sel_names)) + decode_pins = {key: self.column_decoder_inst[port].get_pin(key) for key in decode_names } + column_mux_pins = {key: self.column_mux_array_inst[port].get_pin(key) for key in sel_names } + # Combine the dff and bank pins into a single dictionary of pin name to pin. + all_pins = {**decode_pins, **column_mux_pins} + self.create_vertical_channel_route(route_map, all_pins, offset) + + def route_column_address_lines_right(self, port): + """ Connecting the select lines of column mux to the address bus """ + if not self.col_addr_size>0: + return + + if self.col_addr_size == 1: + + # Connect to sel[0] and sel[1] + decode_names = ["Zb", "Z"] + + # The Address LSB + self.copy_layout_pin(self.column_decoder_inst[port], "A", "addr{}_0".format(port)) - # The Address LSB - self.copy_layout_pin(self.col_decoder_inst[port], "A", "addr{}_0".format(port)) - - elif self.col_addr_size > 1: - decode_names = [] - for i in range(self.num_col_addr_lines): - decode_names.append("out_{}".format(i)) + elif self.col_addr_size > 1: + decode_names = [] + for i in range(self.num_col_addr_lines): + decode_names.append("out_{}".format(i)) - for i in range(self.col_addr_size): - decoder_name = "in_{}".format(i) - addr_name = "addr{0}_{1}".format(port,i) - self.copy_layout_pin(self.col_decoder_inst[port], decoder_name, addr_name) + for i in range(self.col_addr_size): + decoder_name = "in_{}".format(i) + addr_name = "addr{0}_{1}".format(port,i) + self.copy_layout_pin(self.column_decoder_inst[port], decoder_name, addr_name) - offset = self.col_decoder_inst[port].lr() + vector(self.m2_pitch, 0) + offset = self.column_decoder_inst[port].ll() - vector(self.num_col_addr_lines*self.m2_pitch, 0) - sel_names = ["sel_{}".format(x) for x in range(self.num_col_addr_lines)] - - route_map = list(zip(decode_names, sel_names)) - decode_pins = {key: self.col_decoder_inst[port].get_pin(key) for key in decode_names } - col_mux_pins = {key: self.col_mux_array_inst[port].get_pin(key) for key in sel_names } - # Combine the dff and bank pins into a single dictionary of pin name to pin. - all_pins = {**decode_pins, **col_mux_pins} - self.create_vertical_channel_route(route_map, all_pins, offset) + sel_names = ["sel_{}".format(x) for x in range(self.num_col_addr_lines)] + route_map = list(zip(decode_names, sel_names)) + decode_pins = {key: self.column_decoder_inst[port].get_pin(key) for key in decode_names } + column_mux_pins = {key: self.column_mux_array_inst[port].get_pin(key) for key in sel_names } + # Combine the dff and bank pins into a single dictionary of pin name to pin. + all_pins = {**decode_pins, **column_mux_pins} + self.create_vertical_channel_route(route_map, all_pins, offset) + def add_lvs_correspondence_points(self): """ This adds some points for easier debugging if LVS goes wrong. @@ -923,16 +1150,17 @@ class bank(design.design): # offset=data_pin.center()) # Add labels on the decoder - for i in range(self.word_size): - data_name = "dec_out_{}".format(i) - pin_name = "in_{}".format(i) - data_pin = self.wordline_driver_inst[0].get_pin(pin_name) - self.add_label(text=data_name, - layer="metal1", - offset=data_pin.center()) + for port in self.write_ports: + for i in range(self.word_size): + data_name = "dec_out_{}".format(i) + pin_name = "in_{}".format(i) + data_pin = self.wordline_driver_inst[port].get_pin(pin_name) + self.add_label(text=data_name, + layer="metal1", + offset=data_pin.center()) - def route_control_lines(self): + def route_control_lines(self, port): """ Route the control lines of the entire bank """ # Make a list of tuples that we will connect. @@ -942,37 +1170,34 @@ class bank(design.design): write_inst = 0 read_inst = 0 - # Control lines for RW ports - for port in self.all_ports: - connection = [] - if port in self.read_ports: - connection.append((self.prefix+"clk_buf_bar{}".format(port), self.precharge_array_inst[port].get_pin("en").lc())) + connection = [] + if port in self.read_ports: + connection.append((self.prefix+"clk_buf_bar{}".format(port), self.precharge_array_inst[port].get_pin("en").lc())) - if port in self.write_ports: - connection.append((self.prefix+"w_en{}".format(port), self.write_driver_array_inst[port].get_pin("en").lc())) + if port in self.write_ports: + connection.append((self.prefix+"w_en{}".format(port), self.write_driver_array_inst[port].get_pin("en").lc())) - if port in self.read_ports: - connection.append((self.prefix+"s_en{}".format(port), self.sense_amp_array_inst[port].get_pin("en").lc())) + if port in self.read_ports: + connection.append((self.prefix+"s_en{}".format(port), self.sense_amp_array_inst[port].get_pin("en").lc())) - for (control_signal, pin_pos) in connection: - control_pos = vector(self.bus_xoffset[control_signal].x ,pin_pos.y) - self.add_path("metal1", [control_pos, pin_pos]) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=control_pos, - rotate=90) - - # 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) - control_x_offset = self.bus_xoffset[control_signal].x - control_pos = vector(control_x_offset + self.m1_width, mid_pos.y) - self.add_wire(("metal1","via1","metal2"),[pin_pos, mid_pos, control_pos]) - control_via_pos = vector(control_x_offset, mid_pos.y) + for (control_signal, pin_pos) in connection: + control_pos = vector(self.bus_xoffset[port][control_signal].x ,pin_pos.y) + self.add_path("metal1", [control_pos, pin_pos]) self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=control_via_pos, + offset=control_pos, rotate=90) + # 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) + 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]) + self.add_via_center(layers=("metal1", "via1", "metal2"), + offset=control_pos, + rotate=90) + def analytical_delay(self, vdd, slew, load): """ return analytical delay of the bank""" diff --git a/compiler/modules/bank_select.py b/compiler/modules/bank_select.py index 2b78f739..7141de2a 100644 --- a/compiler/modules/bank_select.py +++ b/compiler/modules/bank_select.py @@ -27,12 +27,12 @@ class bank_select(design.design): def create_netlist(self): self.add_pins() self.add_modules() - self.create_modules() + self.create_instances() def create_layout(self): self.calculate_module_offsets() - self.place_modules() - self.route_modules() + self.place_instances() + self.route_instances() self.DRC_LVS() @@ -99,7 +99,7 @@ class bank_select(design.design): self.height = self.yoffset_maxpoint + 2*self.m1_pitch self.width = self.xoffset_inv + self.inv4x.width - def create_modules(self): + def create_instances(self): self.bank_sel_inv=self.add_inst(name="bank_sel_inv", mod=self.inv_sel) @@ -152,7 +152,7 @@ class bank_select(design.design): "vdd", "gnd"]) - def place_modules(self): + def place_instances(self): # bank select inverter self.bank_select_inv_position = vector(self.xoffset_bank_sel_inv, 0) @@ -195,7 +195,7 @@ class bank_select(design.design): mirror=mirror) - def route_modules(self): + def route_instances(self): # bank_sel is vertical wire bank_sel_inv_pin = self.bank_sel_inv.get_pin("A") diff --git a/compiler/modules/bitcell_array.py b/compiler/modules/bitcell_array.py index 8328a0cf..1d9b1539 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -34,7 +34,7 @@ class bitcell_array(design.design): """ Create and connect the netlist """ self.add_modules() self.add_pins() - self.create_modules() + self.create_instances() def create_layout(self): @@ -85,7 +85,7 @@ class bitcell_array(design.design): self.cell = self.mod_bitcell() self.add_mod(self.cell) - def create_modules(self): + def create_instances(self): """ Create the module instances used in this design """ self.cell_inst = {} for col in range(self.column_size): diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 4ac32967..31e239a4 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -41,12 +41,12 @@ class control_logic(design.design): self.setup_signal_busses() self.add_pins() self.add_modules() - self.create_modules() + self.create_instances() def create_layout(self): """ Create layout and route between modules """ self.route_rails() - self.place_modules() + self.place_instances() self.route_all() #self.add_lvs_correspondence_points() @@ -155,8 +155,8 @@ class control_logic(design.design): self.rail_offsets = self.create_vertical_bus("metal2", self.m2_pitch, offset, self.internal_bus_list, height) - def create_modules(self): - """ Create all the modules """ + def create_instances(self): + """ Create all the instances """ self.create_dffs() self.create_clk_row() if (self.port_type == "rw") or (self.port_type == "w"): @@ -167,8 +167,8 @@ class control_logic(design.design): self.create_rbl() - def place_modules(self): - """ Place all the modules """ + def place_instances(self): + """ Place all the instances """ # Keep track of all right-most instances to determine row boundary # and add the vdd/gnd pins self.row_end_inst = [] diff --git a/compiler/modules/dff_buf.py b/compiler/modules/dff_buf.py index 395216bf..6361b220 100644 --- a/compiler/modules/dff_buf.py +++ b/compiler/modules/dff_buf.py @@ -36,13 +36,13 @@ class dff_buf(design.design): def create_netlist(self): self.add_modules() self.add_pins() - self.create_modules() + self.create_instances() def create_layout(self): self.width = self.dff.width + self.inv1.width + self.inv2.width self.height = self.dff.height - self.place_modules() + self.place_instances() self.route_wires() self.add_layout_pins() self.DRC_LVS() @@ -70,7 +70,7 @@ class dff_buf(design.design): self.add_pin("vdd") self.add_pin("gnd") - def create_modules(self): + def create_instances(self): self.dff_inst=self.add_inst(name="dff_buf_dff", mod=self.dff) self.connect_inst(["D", "qint", "clk", "vdd", "gnd"]) @@ -83,7 +83,7 @@ class dff_buf(design.design): mod=self.inv2) self.connect_inst(["Qb", "Q", "vdd", "gnd"]) - def place_modules(self): + def place_instances(self): # Add the DFF self.dff_inst.place(vector(0,0)) diff --git a/compiler/modules/hierarchical_predecode.py b/compiler/modules/hierarchical_predecode.py index dc6622ed..72d0d99f 100644 --- a/compiler/modules/hierarchical_predecode.py +++ b/compiler/modules/hierarchical_predecode.py @@ -31,8 +31,8 @@ class hierarchical_predecode(design.design): self.add_pin("vdd") self.add_pin("gnd") - def create_modules(self): - """ Create the INV and NAND gate """ + def add_modules(self): + """ Add the INV and NAND gate modules """ self.inv = pinv() self.add_mod(self.inv) diff --git a/compiler/modules/hierarchical_predecode2x4.py b/compiler/modules/hierarchical_predecode2x4.py index 813cbf81..4a7609bd 100644 --- a/compiler/modules/hierarchical_predecode2x4.py +++ b/compiler/modules/hierarchical_predecode2x4.py @@ -18,7 +18,7 @@ class hierarchical_predecode2x4(hierarchical_predecode): def create_netlist(self): self.add_pins() - self.create_modules() + self.add_modules() self.create_input_inverters() self.create_output_inverters() connections =[["inbar_0", "inbar_1", "Z_0", "vdd", "gnd"], diff --git a/compiler/modules/hierarchical_predecode3x8.py b/compiler/modules/hierarchical_predecode3x8.py index c8cac345..f0bb4b39 100644 --- a/compiler/modules/hierarchical_predecode3x8.py +++ b/compiler/modules/hierarchical_predecode3x8.py @@ -18,7 +18,7 @@ class hierarchical_predecode3x8(hierarchical_predecode): def create_netlist(self): self.add_pins() - self.create_modules() + self.add_modules() self.create_input_inverters() self.create_output_inverters() connections=[["inbar_0", "inbar_1", "inbar_2", "Z_0", "vdd", "gnd"], diff --git a/compiler/modules/multibank.py b/compiler/modules/multibank.py index 5de24948..d402b145 100644 --- a/compiler/modules/multibank.py +++ b/compiler/modules/multibank.py @@ -54,8 +54,8 @@ class multibank(design.design): self.compute_sizes() self.add_pins() - self.create_modules() self.add_modules() + self.create_instances() self.setup_layout_constraints() # FIXME: Move this to the add modules function @@ -111,7 +111,7 @@ class multibank(design.design): self.route_supplies() - def add_modules(self): + def create_instances(self): """ Add modules. The order should not matter! """ # Above the bitcell array @@ -175,8 +175,8 @@ class multibank(design.design): - def create_modules(self): - """ Create all the modules using the class loader """ + def add_modules(self): + """ Add all the modules using the class loader """ self.tri = self.mod_tri_gate() self.bitcell = self.mod_bitcell() diff --git a/compiler/modules/replica_bitline.py b/compiler/modules/replica_bitline.py index 32e87c34..5c14b14d 100644 --- a/compiler/modules/replica_bitline.py +++ b/compiler/modules/replica_bitline.py @@ -29,11 +29,11 @@ class replica_bitline(design.design): def create_netlist(self): self.add_modules() self.add_pins() - self.create_modules() + self.create_instances() def create_layout(self): self.calculate_module_offsets() - self.place_modules() + self.place_instances() self.route() self.add_layout_pins() @@ -104,7 +104,7 @@ class replica_bitline(design.design): self.access_tx = ptx(tx_type="pmos") self.add_mod(self.access_tx) - def create_modules(self): + def create_instances(self): """ Create all of the module instances in the logical netlist """ # This is the threshold detect inverter on the output of the RBL @@ -152,7 +152,7 @@ class replica_bitline(design.design): self.wl_list = self.rbl.cell.list_all_wl_names() self.bl_list = self.rbl.cell.list_all_bl_names() - def place_modules(self): + def place_instances(self): """ Add all of the module instances in the logical netlist """ # This is the threshold detect inverter on the output of the RBL diff --git a/compiler/modules/wordline_driver.py b/compiler/modules/wordline_driver.py index dd1039b0..ed379bcc 100644 --- a/compiler/modules/wordline_driver.py +++ b/compiler/modules/wordline_driver.py @@ -129,16 +129,15 @@ class wordline_driver(design.design): nand2_xoffset = inv1_xoffset + self.inv.width inv2_xoffset = nand2_xoffset + self.nand2.width - self.width = inv2_xoffset + self.inv.height - driver_height = self.inv.height + self.width = inv2_xoffset + self.inv.width self.height = self.inv.height * self.rows for row in range(self.rows): if (row % 2): - y_offset = driver_height*(row + 1) + y_offset = self.inv.height*(row + 1) inst_mirror = "MX" else: - y_offset = driver_height*row + y_offset = self.inv.height*row inst_mirror = "R0" inv1_offset = [inv1_xoffset, y_offset] diff --git a/compiler/options.py b/compiler/options.py index 3a9e68c0..bd4bf607 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -20,8 +20,10 @@ class options(optparse.Values): debug_level = 0 # When enabled, layout is not generated (and no DRC or LVS are performed) netlist_only = False - # This determines whether LVS and DRC is checked for each submodule. + # This determines whether LVS and DRC is checked at all. check_lvsdrc = True + # This determines whether LVS and DRC is checked for every submodule. + inline_lvsdrc = False # Variable to select the variant of spice spice_name = "" # The spice executable being used which is derived from the user PATH. diff --git a/compiler/router/router.py b/compiler/router/router.py index 76f2e1cc..18d88df7 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -653,63 +653,32 @@ class router(router_tech): debug.info(2,"Analyzing pin groups for {}.".format(pin_name)) pin_set = self.pins[pin_name] - local_debug = False # Put each pin in an equivalence class of it's own equiv_classes = [set([x]) for x in pin_set] - if local_debug: - debug.info(0,"INITIAL\n",equiv_classes) - - def compare_classes(class1, class2): - """ - Determine if two classes should be combined and if so return - the combined set. Otherwise, return None. - """ - if local_debug: - debug.info(0,"CLASS1:\n",class1) - debug.info(0,"CLASS2:\n",class2) - # Compare each pin in each class, - # and if any overlap, return the combined the class - for p1 in class1: - for p2 in class2: - if p1.overlaps(p2): - combined_class = class1 | class2 - if local_debug: - debug.info(0,"COMBINE:",pformat(combined_class)) - return combined_class - - if local_debug: - debug.info(0,"NO COMBINE") - return None - - def combine_classes(equiv_classes): - """ Recursive function to combine classes. """ - local_debug = False - - if local_debug: - debug.info(0,"\nRECURSE:\n",pformat(equiv_classes)) - if len(equiv_classes)==1: - return(equiv_classes) - for class1 in equiv_classes: for class2 in equiv_classes: if class1 == class2: continue - class3 = compare_classes(class1, class2) - if class3: - new_classes = equiv_classes - new_classes.remove(class1) - new_classes.remove(class2) - new_classes.append(class3) - return(combine_classes(new_classes)) - else: - return(equiv_classes) + # Compare each pin in each class, + # and if any overlap, update equiv_classes to include the combined the class + for p1 in class1: + for p2 in class2: + if p1.overlaps(p2): + combined_class = class1 | class2 + equiv_classes.remove(class1) + equiv_classes.remove(class2) + equiv_classes.append(combined_class) + return(equiv_classes) + return(equiv_classes) - reduced_classes = combine_classes(equiv_classes) - if local_debug: - debug.info(0,"FINAL ",reduced_classes) - self.pin_groups[pin_name] = [pin_group(name=pin_name, pin_set=x, router=self) for x in reduced_classes] + old_length = math.inf + while (len(equiv_classes)0: - self.control_logic = self.control_logic_rw = control_logic(num_rows=self.num_rows, words_per_row=self.words_per_row, port_type="rw") + if len(self.readwrite_ports)>0: + self.control_logic_rw = self.mod_control_logic(num_rows=self.num_rows, + words_per_row=self.words_per_row, + port_type="rw") self.add_mod(self.control_logic_rw) - if OPTS.num_w_ports>0: - self.control_logic_w = control_logic(num_rows=self.num_rows, words_per_row=self.words_per_row, port_type="w") + if len(self.write_ports)>0: + self.control_logic_w = self.mod_control_logic(num_rows=self.num_rows, + words_per_row=self.words_per_row, + port_type="w") self.add_mod(self.control_logic_w) - if OPTS.num_r_ports>0: - self.control_logic_r = control_logic(num_rows=self.num_rows, words_per_row=self.words_per_row, port_type="r") + if len(self.read_ports)>0: + self.control_logic_r = self.mod_control_logic(num_rows=self.num_rows, + words_per_row=self.words_per_row, + port_type="r") self.add_mod(self.control_logic_r) # Create the address and control flops (but not the clk) @@ -382,7 +386,8 @@ class sram_base(design): def create_control_logic(self): - """ Add and place control logic """ + """ Add control logic instances """ + insts = [] for port in self.all_ports: if port in self.readwrite_ports: @@ -392,8 +397,7 @@ class sram_base(design): else: mod = self.control_logic_r - insts.append(self.add_inst(name="control{}".format(port), - mod=mod)) + insts.append(self.add_inst(name="control{}".format(port), mod=mod)) temp = ["csb{}".format(port)] if port in self.readwrite_ports: diff --git a/compiler/tests/19_single_bank_1rw_1r_test.py b/compiler/tests/19_single_bank_1rw_1r_test.py new file mode 100755 index 00000000..93bfd35a --- /dev/null +++ b/compiler/tests/19_single_bank_1rw_1r_test.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +""" +Run a regression test on 1rw 1r sram bank +""" + +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 single_bank_1rw_1r_test(openram_test): + + def runTest(self): + globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + from bank import bank + + OPTS.bitcell = "bitcell_1rw_1r" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + + from sram_config import sram_config + c = sram_config(word_size=4, + num_words=16) + + c.words_per_row=1 + debug.info(1, "No column mux") + a = bank(c, name="bank1_single") + self.local_check(a) + + c.num_words=32 + c.words_per_row=2 + debug.info(1, "Two way column mux") + a = bank(c, name="bank2_single") + self.local_check(a) + + c.num_words=64 + c.words_per_row=4 + debug.info(1, "Four way column mux") + a = bank(c, name="bank3_single") + self.local_check(a) + + c.word_size=2 + c.num_words=128 + c.words_per_row=8 + debug.info(1, "Eight way column mux") + a = bank(c, name="bank4_single") + 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/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.svg b/images/download.svg new file mode 100644 index 00000000..95d978ed --- /dev/null +++ b/images/download.svg @@ -0,0 +1,2 @@ + +download download latestlatest diff --git a/images/license_badge.svg b/images/license_badge.svg new file mode 100644 index 00000000..bc36cde7 --- /dev/null +++ b/images/license_badge.svg @@ -0,0 +1 @@ + LicenseLicenseBSD 3-ClauseBSD 3-Clause \ No newline at end of file