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
-[](https://github.com/VLSIDA/PrivateRAM/commits)
-[](https://github.com/VLSIDA/PrivateRAM/archive/dev.zip)
+
+[](https://www.python.org/)
[](./LICENSE)
+Master:
+[](https://github.com/VLSIDA/PrivateRAM/commits/master)
+
+[](https://github.com/VLSIDA/PrivateRAM/archive/master.zip)
+
+Dev:
+[](https://github.com/VLSIDA/PrivateRAM/commits/dev)
+
+[](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 @@
-
-
-