Merge branch 'dev' into laptop_checkpoint

This commit is contained in:
Jesse Cirimelli-Low 2021-04-23 22:50:23 -07:00
commit 6ea4bdc5e5
70 changed files with 1097 additions and 490 deletions

3
.gitattributes vendored
View File

@ -1 +1,2 @@
*.sp linguist-vendored *.sp linguist-language=Spice
*.tf linquist-language=Tech File

View File

@ -9,17 +9,17 @@ jobs:
- name: SCMOS test - name: SCMOS test
run: | run: |
. /home/github-runner/setup-paths.sh . /home/github-runner/setup-paths.sh
export OPENRAM_HOME="`pwd`/compiler" export OPENRAM_HOME="${{ github.workspace }}/compiler"
export OPENRAM_TECH="`pwd`/technology:/software/PDKs/skywater-tech" export OPENRAM_TECH="${{ github.workspace }}/technology:/software/PDKs/skywater-tech"
export OPENRAM_TMP="${{ github.workspace }}/scn4me_subm_temp" export OPENRAM_TMP="${{ github.workspace }}/scn4me_subm_temp"
#python3-coverage run -p $OPENRAM_HOME/tests/regress.py -j 12 -t scn4m_subm #python3-coverage run -p $OPENRAM_HOME/tests/regress.py -j 12 -t scn4m_subm
$OPENRAM_HOME/tests/regress.py -j 12 -t scn4m_subm $OPENRAM_HOME/tests/regress.py -j 24 -t scn4m_subm
- name: Archive - name: Archive
if: ${{ failure() }} if: ${{ failure() }}
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: scn4me_subm Archives name: scn4me_subm Archives
path: ${{ github.workspace }}/scn4me_subm_temp/*/* path: ${{ github.workspace }}/*.zip
freepdk45: freepdk45:
runs-on: self-hosted runs-on: self-hosted
steps: steps:
@ -28,17 +28,17 @@ jobs:
- name: FreePDK45 test - name: FreePDK45 test
run: | run: |
. /home/github-runner/setup-paths.sh . /home/github-runner/setup-paths.sh
export OPENRAM_HOME="`pwd`/compiler" export OPENRAM_HOME="${{ github.workspace }}/compiler"
export OPENRAM_TECH="`pwd`/technology:/software/PDKs/skywater-tech" export OPENRAM_TECH="${{ github.workspace }}/technology:/software/PDKs/skywater-tech"
export OPENRAM_TMP="${{ github.workspace }}/freepdk45_temp" export OPENRAM_TMP="${{ github.workspace }}/freepdk45_temp"
#python3-coverage run -p $OPENRAM_HOME/tests/regress.py -j 12 -t freepdk45 #python3-coverage run -p $OPENRAM_HOME/tests/regress.py -j 12 -t freepdk45
$OPENRAM_HOME/tests/regress.py -j 12 -t freepdk45 $OPENRAM_HOME/tests/regress.py -j 24 -t freepdk45
- name: Archive - name: Archive
if: ${{ failure() }} if: ${{ failure() }}
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: FreePDK45 Archives name: FreePDK45 Archives
path: ${{ github.workspace }}/freepdk45_temp/*/* path: ${{ github.workspace }}/*.zip
# coverage_stats: # coverage_stats:
# if: ${{ always() }} # if: ${{ always() }}
# needs: [scn4me_subm, freepdk45] # needs: [scn4me_subm, freepdk45]

View File

@ -1,12 +1,9 @@
![](./images/OpenRAM_logo_yellow_transparent.svg)
# OpenRAM # OpenRAM
[![Python 3.5](https://img.shields.io/badge/Python-3.5-green.svg)](https://www.python.org/) [![Python 3.5](https://img.shields.io/badge/Python-3.5-green.svg)](https://www.python.org/)
[![License: BSD 3-clause](./images/license_badge.svg)](./LICENSE) [![License: BSD 3-clause](./images/license_badge.svg)](./LICENSE)
[![Download](./images/download-stable-blue.svg)](https://github.com/VLSIDA/OpenRAM/archive/stable.zip)
Master:
[![Download](./images/download-stable-blue.svg)](https://github.com/VLSIDA/OpenRAM/archive/master.zip)
Dev:
[![Download](./images/download-unstable-blue.svg)](https://github.com/VLSIDA/OpenRAM/archive/dev.zip) [![Download](./images/download-unstable-blue.svg)](https://github.com/VLSIDA/OpenRAM/archive/dev.zip)
An open-source static random access memory (SRAM) compiler. An open-source static random access memory (SRAM) compiler.
@ -34,8 +31,7 @@ things that need to be fixed.
The OpenRAM compiler has very few dependencies: The OpenRAM compiler has very few dependencies:
+ [Ngspice] 26 (or later) or HSpice I-2013.12-1 (or later) or CustomSim 2017 (or later) + [Ngspice] 26 (or later) or HSpice I-2013.12-1 (or later) or CustomSim 2017 (or later)
+ Python 3.5 or higher + Python 3.5 or higher
+ Python numpy (pip3 install numpy to install) + Various Python packages (pip install -r requirements.txt)
+ Python scipy (pip3 install scipy to install)
If you want to perform DRC and LVS, you will need either: If you want to perform DRC and LVS, you will need either:
+ Calibre (for [FreePDK45]) + Calibre (for [FreePDK45])
@ -145,7 +141,7 @@ The default for openram.py is specified in the configuration file.
# Porting to a New Technology # Porting to a New Technology
If you want to support a enw technology, you will need to create: If you want to support a new technology, you will need to create:
+ a setup script for each technology you want to use + a setup script for each technology you want to use
+ a technology directory for each technology with the base cells + a technology directory for each technology with the base cells

View File

@ -123,6 +123,14 @@ class _wordline_driver:
self.vertical_supply = vertical_supply self.vertical_supply = vertical_supply
class _bitcell_array:
def __init__(self,
wordline_layer,
wordline_pitch_factor=2):
self.wordline_layer = wordline_layer
self.wordline_pitch_factor = wordline_pitch_factor
class layer_properties(): class layer_properties():
""" """
This contains meta information about the module routing layers. These This contains meta information about the module routing layers. These
@ -159,6 +167,10 @@ class layer_properties():
self._wordline_driver = _wordline_driver(vertical_supply=False) self._wordline_driver = _wordline_driver(vertical_supply=False)
self._local_bitcell_array = _bitcell_array(wordline_layer="m2")
self._global_bitcell_array = _bitcell_array(wordline_layer="m3")
@property @property
def bank(self): def bank(self):
return self._bank return self._bank
@ -191,3 +203,11 @@ class layer_properties():
def wordline_driver(self): def wordline_driver(self):
return self._wordline_driver return self._wordline_driver
@property
def global_bitcell_array(self):
return self._global_bitcell_array
@property
def local_bitcell_array(self):
return self._local_bitcell_array

View File

@ -53,7 +53,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
elif (OPTS.inline_lvsdrc or force_check or final_verification): elif (OPTS.inline_lvsdrc or force_check or final_verification):
tempspice = "{}.sp".format(self.name) tempspice = "{}.sp".format(self.name)
self.lvs_write("{0}{1}".format(OPTS.openram_temp, tempspice)) self.sp_write("{0}{1}".format(OPTS.openram_temp, tempspice), lvs=True)
tempgds = "{}.gds".format(self.name) tempgds = "{}.gds".format(self.name)
self.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds)) self.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds))
# Final verification option does not allow nets to be connected by label. # Final verification option does not allow nets to be connected by label.
@ -82,7 +82,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
return return
elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)): elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
tempspice = "{}.sp".format(self.name) tempspice = "{}.sp".format(self.name)
self.lvs_write("{0}{1}".format(OPTS.openram_temp, tempspice)) self.sp_write("{0}{1}".format(OPTS.openram_temp, tempspice), lvs=True)
tempgds = "{}.gds".format(self.cell_name) tempgds = "{}.gds".format(self.cell_name)
self.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds)) self.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds))
num_errors = verify.run_drc(self.cell_name, tempgds, tempspice, final_verification=final_verification) num_errors = verify.run_drc(self.cell_name, tempgds, tempspice, final_verification=final_verification)
@ -102,7 +102,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
return return
elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)): elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
tempspice = "{}.sp".format(self.cell_name) tempspice = "{}.sp".format(self.cell_name)
self.lvs_write("{0}{1}".format(OPTS.openram_temp, tempspice)) self.sp_write("{0}{1}".format(OPTS.openram_temp, tempspice), lvs=True)
tempgds = "{}.gds".format(self.name) tempgds = "{}.gds".format(self.name)
self.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds)) self.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds))
num_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification=final_verification) num_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification=final_verification)

View File

@ -15,6 +15,7 @@ from tech import layer_indices
from tech import layer_stacks from tech import layer_stacks
from tech import preferred_directions from tech import preferred_directions
import os import os
import sys
from globals import OPTS from globals import OPTS
from vector import vector from vector import vector
from pin_layout import pin_layout from pin_layout import pin_layout
@ -111,52 +112,48 @@ class layout():
Finds the lowest set of 2d cartesian coordinates within Finds the lowest set of 2d cartesian coordinates within
this layout this layout
""" """
lowestx = lowesty = sys.maxsize
if len(self.objs) > 0: if len(self.objs) > 0:
lowestx1 = min(obj.lx() for obj in self.objs if obj.name != "label") lowestx = min(min(obj.lx() for obj in self.objs if obj.name != "label"), lowestx)
lowesty1 = min(obj.by() for obj in self.objs if obj.name != "label") lowesty = min(min(obj.by() for obj in self.objs if obj.name != "label"), lowesty)
else:
lowestx1 = lowesty1 = None
if len(self.insts) > 0: if len(self.insts) > 0:
lowestx2 = min(inst.lx() for inst in self.insts) lowestx = min(min(inst.lx() for inst in self.insts), lowestx)
lowesty2 = min(inst.by() for inst in self.insts) lowesty = min(min(inst.by() for inst in self.insts), lowesty)
else:
lowestx2 = lowesty2 = None
if lowestx1 == None and lowestx2 == None: if len(self.pin_map) > 0:
return None for pin_set in self.pin_map.values():
elif lowestx1 == None: if len(pin_set) == 0:
return vector(lowestx2, lowesty2) continue
elif lowestx2 == None: lowestx = min(min(pin.lx() for pin in pin_set), lowestx)
return vector(lowestx1, lowesty1) lowesty = min(min(pin.by() for pin in pin_set), lowesty)
else:
return vector(min(lowestx1, lowestx2), min(lowesty1, lowesty2)) return vector(lowestx, lowesty)
def find_highest_coords(self): def find_highest_coords(self):
""" """
Finds the highest set of 2d cartesian coordinates within Finds the highest set of 2d cartesian coordinates within
this layout this layout
""" """
highestx = highesty = -sys.maxsize - 1
if len(self.objs) > 0: if len(self.objs) > 0:
highestx1 = max(obj.rx() for obj in self.objs if obj.name != "label") highestx = max(max(obj.rx() for obj in self.objs if obj.name != "label"), highestx)
highesty1 = max(obj.uy() for obj in self.objs if obj.name != "label") highesty = max(max(obj.uy() for obj in self.objs if obj.name != "label"), highesty)
else:
highestx1 = highesty1 = None
if len(self.insts) > 0:
highestx2 = max(inst.rx() for inst in self.insts)
highesty2 = max(inst.uy() for inst in self.insts)
else:
highestx2 = highesty2 = None
if highestx1 == None and highestx2 == None: if len(self.insts) > 0:
return None highestx = max(max(inst.rx() for inst in self.insts), highestx)
elif highestx1 == None: highesty = max(max(inst.uy() for inst in self.insts), highesty)
return vector(highestx2, highesty2)
elif highestx2 == None: if len(self.pin_map) > 0:
return vector(highestx1, highesty1) for pin_set in self.pin_map.values():
else: if len(pin_set) == 0:
return vector(max(highestx1, highestx2), continue
max(highesty1, highesty2)) highestx = max(max(pin.rx() for pin in pin_set), highestx)
highesty = max(max(pin.uy() for pin in pin_set), highesty)
return vector(highestx, highesty)
def find_highest_layer_coords(self, layer): def find_highest_layer_coords(self, layer):
""" """
@ -677,7 +674,8 @@ class layout():
directions=None, directions=None,
size=[1, 1], size=[1, 1],
implant_type=None, implant_type=None,
well_type=None): well_type=None,
min_area=False):
""" """
Punch a stack of vias from a start layer to a target layer by the center. Punch a stack of vias from a start layer to a target layer by the center.
""" """
@ -711,7 +709,7 @@ class layout():
implant_type=implant_type, implant_type=implant_type,
well_type=well_type) well_type=well_type)
if cur_layer != from_layer: if cur_layer != from_layer or min_area:
self.add_min_area_rect_center(cur_layer, self.add_min_area_rect_center(cur_layer,
offset, offset,
via.mod.first_layer_width, via.mod.first_layer_width,

View File

@ -63,6 +63,8 @@ class spice():
self.conns = [] self.conns = []
# If this is set, it will out output subckt or isntances of this (for row/col caps etc.) # If this is set, it will out output subckt or isntances of this (for row/col caps etc.)
self.no_instances = False self.no_instances = False
# If we are doing a trimmed netlist, these are the instance that will be filtered
self.trim_insts = set()
# Keep track of any comments to add the the spice # Keep track of any comments to add the the spice
try: try:
self.commments self.commments
@ -312,10 +314,11 @@ class spice():
return True return True
return False return False
def sp_write_file(self, sp, usedMODS, lvs_netlist=False): def sp_write_file(self, sp, usedMODS, lvs=False, trim=False):
""" """
Recursive spice subcircuit write; Recursive spice subcircuit write;
Writes the spice subcircuit from the library or the dynamically generated one Writes the spice subcircuit from the library or the dynamically generated one.
Trim netlist is intended ONLY for bitcell arrays.
""" """
if self.no_instances: if self.no_instances:
@ -328,7 +331,7 @@ class spice():
if self.contains(i, usedMODS): if self.contains(i, usedMODS):
continue continue
usedMODS.append(i) usedMODS.append(i)
i.sp_write_file(sp, usedMODS, lvs_netlist) i.sp_write_file(sp, usedMODS, lvs, trim)
if len(self.insts) == 0: if len(self.insts) == 0:
return return
@ -371,10 +374,16 @@ class spice():
# these are wires and paths # these are wires and paths
if self.conns[i] == []: if self.conns[i] == []:
continue continue
# Instance with no devices in it needs no subckt/instance # Instance with no devices in it needs no subckt/instance
if self.insts[i].mod.no_instances: if self.insts[i].mod.no_instances:
continue continue
if lvs_netlist and hasattr(self.insts[i].mod, "lvs_device"):
# If this is a trimmed netlist, skip it by adding comment char
if trim and self.insts[i].name in self.trim_insts:
sp.write("* ")
if lvs and hasattr(self.insts[i].mod, "lvs_device"):
sp.write(self.insts[i].mod.lvs_device.format(self.insts[i].name, sp.write(self.insts[i].mod.lvs_device.format(self.insts[i].name,
" ".join(self.conns[i]))) " ".join(self.conns[i])))
sp.write("\n") sp.write("\n")
@ -394,30 +403,20 @@ class spice():
# Including the file path makes the unit test fail for other users. # Including the file path makes the unit test fail for other users.
# if os.path.isfile(self.sp_file): # if os.path.isfile(self.sp_file):
# sp.write("\n* {0}\n".format(self.sp_file)) # sp.write("\n* {0}\n".format(self.sp_file))
if lvs_netlist and hasattr(self, "lvs"): if lvs and hasattr(self, "lvs"):
sp.write("\n".join(self.lvs)) sp.write("\n".join(self.lvs))
else: else:
sp.write("\n".join(self.spice)) sp.write("\n".join(self.spice))
sp.write("\n") sp.write("\n")
def sp_write(self, spname): def sp_write(self, spname, lvs=False, trim=False):
"""Writes the spice to files""" """Writes the spice to files"""
debug.info(3, "Writing to {0}".format(spname)) debug.info(3, "Writing to {0}".format(spname))
spfile = open(spname, 'w') spfile = open(spname, 'w')
spfile.write("*FIRST LINE IS A COMMENT\n") spfile.write("*FIRST LINE IS A COMMENT\n")
usedMODS = list() usedMODS = list()
self.sp_write_file(spfile, usedMODS) self.sp_write_file(spfile, usedMODS, lvs=lvs, trim=trim)
del usedMODS
spfile.close()
def lvs_write(self, spname):
"""Writes the lvs to files"""
debug.info(3, "Writing to {0}".format(spname))
spfile = open(spname, 'w')
spfile.write("*FIRST LINE IS A COMMENT\n")
usedMODS = list()
self.sp_write_file(spfile, usedMODS, True)
del usedMODS del usedMODS
spfile.close() spfile.close()

View File

@ -7,6 +7,11 @@
# #
import debug import debug
from tech import layer_names from tech import layer_names
import os
import shutil
from globals import OPTS
from vector import vector
from pin_layout import pin_layout
class lef: class lef:
@ -23,11 +28,105 @@ class lef:
# Round to ensure float values are divisible by 0.0025 (the manufacturing grid) # Round to ensure float values are divisible by 0.0025 (the manufacturing grid)
self.round_grid = 4 self.round_grid = 4
def lef_write(self, lef_name): def magic_lef_write(self, lef_name):
"""Write the entire lef of the object to the file.""" """ Use a magic script to perform LEF creation. """
debug.info(3, "Writing to {0}".format(lef_name)) debug.info(3, "Writing abstracted LEF to {0}".format(lef_name))
self.indent = "" # To maintain the indent level easily # Copy .magicrc file into the output directory
magic_file = OPTS.openram_tech + "tech/.magicrc"
if os.path.exists(magic_file):
shutil.copy(magic_file, OPTS.openram_temp)
else:
debug.warning("Could not locate .magicrc file: {}".format(magic_file))
gds_name = OPTS.openram_temp + "{}.gds".format(self.name)
self.gds_write(gds_name)
run_file = OPTS.openram_temp + "run_lef.sh"
f = open(run_file, "w")
f.write("#!/bin/sh\n")
f.write('export OPENRAM_TECH="{}"\n'.format(os.environ['OPENRAM_TECH']))
f.write('echo "$(date): Starting GDS to MAG using Magic {}"\n'.format(OPTS.drc_exe[1]))
f.write('\n')
f.write("{} -dnull -noconsole << EOF\n".format(OPTS.drc_exe[1]))
f.write("drc off\n")
f.write("gds polygon subcell true\n")
f.write("gds warning default\n")
f.write("gds flatten true\n")
f.write("gds ordering true\n")
f.write("gds readonly true\n")
f.write("gds read {}\n".format(gds_name))
f.write('puts "Finished reading gds {}"\n'.format(gds_name))
f.write("load {}\n".format(self.name))
f.write('puts "Finished loading cell {}"\n'.format(self.name))
f.write("cellname delete \\(UNNAMED\\)\n")
f.write("lef write {} -hide\n".format(lef_name))
f.write('puts "Finished writing LEF cell {}"\n'.format(self.name))
f.close()
os.system("chmod u+x {}".format(run_file))
from run_script import run_script
(outfile, errfile, resultsfile) = run_script(self.name, "lef")
def lef_write(self, lef_name):
""" Write the entire lef of the object to the file. """
if OPTS.detailed_lef:
debug.info(3, "Writing detailed LEF to {0}".format(lef_name))
self.detailed_lef_write(lef_name)
else:
debug.info(3, "Writing abstract LEF to {0}".format(lef_name))
# Can possibly use magic lef write to create the LEF
# if OPTS.drc_exe and OPTS.drc_exe[0] == "magic":
# self.magic_lef_write(lef_name)
# return
self.abstract_lef_write(lef_name)
def abstract_lef_write(self, lef_name):
# To maintain the indent level easily
self.indent = ""
self.lef = open(lef_name, "w")
self.lef_write_header()
# Start with blockages on all layers the size of the block
# minus the pin escape margin (hard coded to 4 x m3 pitch)
# These are a pin_layout to use their geometric functions
perimeter_margin = self.m3_pitch
self.blockages = {}
for layer_name in self.lef_layers:
self.blockages[layer_name]=[]
for layer_name in self.lef_layers:
ll = vector(perimeter_margin, perimeter_margin)
ur = vector(self.width - perimeter_margin, self.height - perimeter_margin)
self.blockages[layer_name].append(pin_layout("",
[ll, ur],
layer_name))
# For each pin, remove the blockage and add the pin
for pin_name in self.pins:
pin = self.get_pin(pin_name)
inflated_pin = pin.inflated_pin(multiple=1)
for blockage in self.blockages[pin.layer]:
if blockage.overlaps(inflated_pin):
intersection_shape = blockage.intersection(inflated_pin)
# If it is zero area, don't add the pin
if intersection_shape[0][0]==intersection_shape[1][0] or intersection_shape[0][1]==intersection_shape[1][1]:
continue
# Remove the old blockage and add the new ones
self.blockages[pin.layer].remove(blockage)
intersection_pin = pin_layout("", intersection_shape, inflated_pin.layer)
new_blockages = blockage.cut(intersection_pin)
self.blockages[pin.layer].extend(new_blockages)
self.lef_write_pin(pin_name)
self.lef_write_obstructions(abstracted=True)
self.lef_write_footer()
self.lef.close()
def detailed_lef_write(self, lef_name):
# To maintain the indent level easily
self.indent = ""
self.lef = open(lef_name, "w") self.lef = open(lef_name, "w")
self.lef_write_header() self.lef_write_header()
@ -89,24 +188,29 @@ class lef:
self.indent = self.indent[:-3] self.indent = self.indent[:-3]
self.lef.write("{0}END {1}\n".format(self.indent, name)) self.lef.write("{0}END {1}\n".format(self.indent, name))
def lef_write_obstructions(self): def lef_write_obstructions(self, abstracted=False):
""" Write all the obstructions on each layer """ """ Write all the obstructions on each layer """
self.lef.write("{0}OBS\n".format(self.indent)) self.lef.write("{0}OBS\n".format(self.indent))
for layer in self.lef_layers: for layer in self.lef_layers:
self.lef.write("{0}LAYER {1} ;\n".format(self.indent, layer_names[layer])) self.lef.write("{0}LAYER {1} ;\n".format(self.indent, layer_names[layer]))
self.indent += " " self.indent += " "
blockages = self.get_blockages(layer, True) if abstracted:
for b in blockages: blockages = self.blockages[layer]
self.lef_write_shape(b) for b in blockages:
self.lef_write_shape(b.rect)
else:
blockages = self.get_blockages(layer, True)
for b in blockages:
self.lef_write_shape(b)
self.indent = self.indent[:-3] self.indent = self.indent[:-3]
self.lef.write("{0}END\n".format(self.indent)) self.lef.write("{0}END\n".format(self.indent))
def lef_write_shape(self, rect): def lef_write_shape(self, obj):
if len(rect) == 2: if len(obj) == 2:
""" Write a LEF rectangle """ """ Write a LEF rectangle """
self.lef.write("{0}RECT ".format(self.indent)) self.lef.write("{0}RECT ".format(self.indent))
for item in rect: for item in obj:
# print(rect) # print(obj)
self.lef.write(" {0} {1}".format(round(item[0], self.lef.write(" {0} {1}".format(round(item[0],
self.round_grid), self.round_grid),
round(item[1], round(item[1],
@ -115,12 +219,10 @@ class lef:
else: else:
""" Write a LEF polygon """ """ Write a LEF polygon """
self.lef.write("{0}POLYGON ".format(self.indent)) self.lef.write("{0}POLYGON ".format(self.indent))
for item in rect: for item in obj:
self.lef.write(" {0} {1}".format(round(item[0], self.lef.write(" {0} {1}".format(round(item[0],
self.round_grid), self.round_grid),
round(item[1], round(item[1],
self.round_grid))) self.round_grid)))
# for i in range(0,len(rect)):
# self.lef.write(" {0} {1}".format(round(rect[i][0],self.round_grid), round(rect[i][1],self.round_grid)))
self.lef.write(" ;\n") self.lef.write(" ;\n")

View File

@ -139,13 +139,13 @@ class pin_layout:
min_area = drc("{}_minarea".format(self.layer)) min_area = drc("{}_minarea".format(self.layer))
pass pass
def inflate(self, spacing=None): def inflate(self, spacing=None, multiple=0.5):
""" """
Inflate the rectangle by the spacing (or other rule) Inflate the rectangle by the spacing (or other rule)
and return the new rectangle. and return the new rectangle.
""" """
if not spacing: if not spacing:
spacing = 0.5*drc("{0}_to_{0}".format(self.layer)) spacing = multiple*drc("{0}_to_{0}".format(self.layer))
(ll, ur) = self.rect (ll, ur) = self.rect
spacing = vector(spacing, spacing) spacing = vector(spacing, spacing)
@ -154,15 +154,23 @@ class pin_layout:
return (newll, newur) return (newll, newur)
def inflated_pin(self, spacing=None, multiple=0.5):
"""
Inflate the rectangle by the spacing (or other rule)
and return the new rectangle.
"""
inflated_area = self.inflate(spacing, multiple)
return pin_layout(self.name, inflated_area, self.layer)
def intersection(self, other): def intersection(self, other):
""" Check if a shape overlaps with a rectangle """ """ Check if a shape overlaps with a rectangle """
(ll, ur) = self.rect (ll, ur) = self.rect
(oll, our) = other.rect (oll, our) = other.rect
min_x = max(ll.x, oll.x) min_x = max(ll.x, oll.x)
max_x = min(ll.x, oll.x) max_x = min(ur.x, our.x)
min_y = max(ll.y, oll.y) min_y = max(ll.y, oll.y)
max_y = min(ll.y, oll.y) max_y = min(ur.y, our.y)
return [vector(min_x, min_y), vector(max_x, max_y)] return [vector(min_x, min_y), vector(max_x, max_y)]
@ -369,11 +377,23 @@ class pin_layout:
debug.info(4, "writing pin (" + str(self.layer) + "):" debug.info(4, "writing pin (" + str(self.layer) + "):"
+ str(self.width()) + "x" + str(self.width()) + "x"
+ str(self.height()) + " @ " + str(self.ll())) + str(self.height()) + " @ " + str(self.ll()))
(layer_num, purpose) = layer[self.layer]
# Try to use the pin layer if it exists, otherwise
# use the regular layer
try: try:
from tech import pin_purpose (pin_layer_num, pin_purpose) = layer[self.layer + "p"]
except KeyError:
(pin_layer_num, pin_purpose) = layer[self.layer]
(layer_num, purpose) = layer[self.layer]
# Try to use a global pin purpose if it exists,
# otherwise, use the regular purpose
try:
from tech import pin_purpose as global_pin_purpose
pin_purpose = global_pin_purpose
except ImportError: except ImportError:
pin_purpose = purpose pass
try: try:
from tech import label_purpose from tech import label_purpose
except ImportError: except ImportError:
@ -385,9 +405,9 @@ class pin_layout:
width=self.width(), width=self.width(),
height=self.height(), height=self.height(),
center=False) center=False)
# Draw a second pin shape too # Draw a second pin shape too if it is different
if pin_purpose != purpose: if not self.same_lpp((pin_layer_num, pin_purpose), (layer_num, purpose)):
newLayout.addBox(layerNumber=layer_num, newLayout.addBox(layerNumber=pin_layer_num,
purposeNumber=pin_purpose, purposeNumber=pin_purpose,
offsetInMicrons=self.ll(), offsetInMicrons=self.ll(),
width=self.width(), width=self.width(),
@ -566,6 +586,30 @@ class pin_layout:
return None return None
def cut(self, shape):
"""
Return a set of shapes that are this shape minus the argument shape.
"""
# Make the unique coordinates in X and Y directions
x_offsets = sorted([self.lx(), self.rx(), shape.lx(), shape.rx()])
y_offsets = sorted([self.by(), self.uy(), shape.by(), shape.uy()])
new_shapes = []
# Create all of the shapes
for x1, x2 in zip(x_offsets[0:], x_offsets[1:]):
if x1==x2:
continue
for y1, y2 in zip(y_offsets[0:], y_offsets[1:]):
if y1==y2:
continue
new_shape = pin_layout("", [vector(x1, y1), vector(x2, y2)], self.lpp)
# Don't add the existing shape in if it overlaps the pin shape
if new_shape.contains(shape):
continue
new_shapes.append(new_shape)
return new_shapes
def same_lpp(self, lpp1, lpp2): def same_lpp(self, lpp1, lpp2):
""" """
Check if the layers and purposes are the same. Check if the layers and purposes are the same.

View File

@ -61,6 +61,8 @@ class verilog:
self.vf.write(" parameter RAM_DEPTH = 1 << ADDR_WIDTH;\n") self.vf.write(" parameter RAM_DEPTH = 1 << ADDR_WIDTH;\n")
self.vf.write(" // FIXME: This delay is arbitrary.\n") self.vf.write(" // FIXME: This delay is arbitrary.\n")
self.vf.write(" parameter DELAY = 3 ;\n") self.vf.write(" parameter DELAY = 3 ;\n")
self.vf.write(" parameter VERBOSE = 1 ; //Set to 0 to only display warnings\n")
self.vf.write(" parameter T_HOLD = 1 ; //Delay to hold dout value after posedge. Value is arbitrary\n")
self.vf.write("\n") self.vf.write("\n")
for port in self.all_ports: for port in self.all_ports:
@ -128,21 +130,21 @@ class verilog:
if port in self.write_ports: if port in self.write_ports:
self.vf.write(" din{0}_reg = din{0};\n".format(port)) self.vf.write(" din{0}_reg = din{0};\n".format(port))
if port in self.read_ports: if port in self.read_ports:
self.vf.write(" dout{0} = {1}'bx;\n".format(port, self.word_size)) self.vf.write(" #(T_HOLD) dout{0} = {1}'bx;\n".format(port, self.word_size))
if port in self.readwrite_ports: if port in self.readwrite_ports:
self.vf.write(" if ( !csb{0}_reg && web{0}_reg ) \n".format(port)) self.vf.write(" if ( !csb{0}_reg && web{0}_reg && VERBOSE ) \n".format(port))
self.vf.write(" $display($time,\" Reading %m addr{0}=%b dout{0}=%b\",addr{0}_reg,mem[addr{0}_reg]);\n".format(port)) self.vf.write(" $display($time,\" Reading %m addr{0}=%b dout{0}=%b\",addr{0}_reg,mem[addr{0}_reg]);\n".format(port))
elif port in self.read_ports: elif port in self.read_ports:
self.vf.write(" if ( !csb{0}_reg ) \n".format(port)) self.vf.write(" if ( !csb{0}_reg && VERBOSE ) \n".format(port))
self.vf.write(" $display($time,\" Reading %m addr{0}=%b dout{0}=%b\",addr{0}_reg,mem[addr{0}_reg]);\n".format(port)) self.vf.write(" $display($time,\" Reading %m addr{0}=%b dout{0}=%b\",addr{0}_reg,mem[addr{0}_reg]);\n".format(port))
if port in self.readwrite_ports: if port in self.readwrite_ports:
self.vf.write(" if ( !csb{0}_reg && !web{0}_reg )\n".format(port)) self.vf.write(" if ( !csb{0}_reg && !web{0}_reg && VERBOSE )\n".format(port))
if self.write_size: if self.write_size:
self.vf.write(" $display($time,\" Writing %m addr{0}=%b din{0}=%b wmask{0}=%b\",addr{0}_reg,din{0}_reg,wmask{0}_reg);\n".format(port)) self.vf.write(" $display($time,\" Writing %m addr{0}=%b din{0}=%b wmask{0}=%b\",addr{0}_reg,din{0}_reg,wmask{0}_reg);\n".format(port))
else: else:
self.vf.write(" $display($time,\" Writing %m addr{0}=%b din{0}=%b\",addr{0}_reg,din{0}_reg);\n".format(port)) self.vf.write(" $display($time,\" Writing %m addr{0}=%b din{0}=%b\",addr{0}_reg,din{0}_reg);\n".format(port))
elif port in self.write_ports: elif port in self.write_ports:
self.vf.write(" if ( !csb{0}_reg )\n".format(port)) self.vf.write(" if ( !csb{0}_reg && VERBOSE )\n".format(port))
if self.write_size: if self.write_size:
self.vf.write(" $display($time,\" Writing %m addr{0}=%b din{0}=%b wmask{0}=%b\",addr{0}_reg,din{0}_reg,wmask{0}_reg);\n".format(port)) self.vf.write(" $display($time,\" Writing %m addr{0}=%b din{0}=%b wmask{0}=%b\",addr{0}_reg,din{0}_reg,wmask{0}_reg);\n".format(port))
else: else:

View File

@ -31,6 +31,8 @@ def parse_spice_list(filename, key):
f = open(full_filename, "r") f = open(full_filename, "r")
except IOError: except IOError:
debug.error("Unable to open spice output file: {0}".format(full_filename),1) debug.error("Unable to open spice output file: {0}".format(full_filename),1)
debug.archive()
contents = f.read() contents = f.read()
f.close() f.close()
# val = re.search(r"{0}\s*=\s*(-?\d+.?\d*\S*)\s+.*".format(key), contents) # val = re.search(r"{0}\s*=\s*(-?\d+.?\d*\S*)\s+.*".format(key), contents)

View File

@ -1100,14 +1100,8 @@ class delay(simulation):
# Set up to trim the netlist here if that is enabled # Set up to trim the netlist here if that is enabled
if OPTS.trim_netlist: if OPTS.trim_netlist:
self.trim_sp_file = "{}reduced.sp".format(OPTS.openram_temp) self.trim_sp_file = "{}trimmed.sp".format(OPTS.openram_temp)
self.trimsp=trim_spice(self.sp_file, self.trim_sp_file) self.sram.sp_write(self.trim_sp_file, lvs=False, trim=True)
self.trimsp.set_configuration(self.num_banks,
self.num_rows,
self.num_cols,
self.word_size,
self.num_spare_rows)
self.trimsp.trim(self.probe_address, self.probe_data)
else: else:
# The non-reduced netlist file when it is disabled # The non-reduced netlist file when it is disabled
self.trim_sp_file = "{}sram.sp".format(OPTS.openram_temp) self.trim_sp_file = "{}sram.sp".format(OPTS.openram_temp)

View File

@ -78,8 +78,6 @@ class elmore(simulation):
port_data[port][mname].append(total_delay.delay / 1e3) port_data[port][mname].append(total_delay.delay / 1e3)
elif "slew" in mname and port in self.read_ports: elif "slew" in mname and port in self.read_ports:
port_data[port][mname].append(total_delay.slew / 1e3) port_data[port][mname].append(total_delay.slew / 1e3)
else:
debug.error("Measurement name not recognized: {}".format(mname), 1)
# Margin for error in period. Calculated by averaging required margin for a small and large # Margin for error in period. Calculated by averaging required margin for a small and large
# memory. FIXME: margin is quite large, should be looked into. # memory. FIXME: margin is quite large, should be looked into.

View File

@ -9,6 +9,7 @@ import collections
import debug import debug
import random import random
import math import math
from numpy import binary_repr
from .stimuli import * from .stimuli import *
from .charutils import * from .charutils import *
from globals import OPTS from globals import OPTS
@ -21,13 +22,17 @@ class functional(simulation):
for successful SRAM operation. for successful SRAM operation.
""" """
def __init__(self, sram, spfile, corner=None, cycles=15, period=None, output_path=None): def __init__(self, sram, spfile=None, corner=None, cycles=15, period=None, output_path=None):
super().__init__(sram, spfile, corner) super().__init__(sram, spfile, corner)
# Seed the characterizer with a constant seed for unit tests # Seed the characterizer with a constant seed for unit tests
if OPTS.is_unit_test: if OPTS.is_unit_test:
random.seed(12345) random.seed(12345)
if not spfile:
# self.sp_file is assigned in base class
sram.sp_write(self.sp_file, trim=OPTS.trim_netlist)
if not corner: if not corner:
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
@ -47,6 +52,22 @@ class functional(simulation):
if not self.num_spare_cols: if not self.num_spare_cols:
self.num_spare_cols = 0 self.num_spare_cols = 0
self.max_data = 2 ** self.word_size - 1
self.max_col_data = 2 ** self.num_spare_cols - 1
if self.words_per_row>1:
# This will truncate bits for word addressing in a row_addr_dff
# This makes one set of spares per row by using top bits of the address
self.addr_spare_index = -int(math.log(self.words_per_row) / math.log(2))
else:
# This will select the entire address when one word per row
self.addr_spare_index = self.addr_size
# If trim is set, specify the valid addresses
self.valid_addresses = set()
self.max_address = 2**self.addr_size - 1 + (self.num_spare_rows * self.words_per_row)
if OPTS.trim_netlist:
for i in range(self.words_per_row):
self.valid_addresses.add(i)
self.valid_addresses.add(self.max_address - i)
self.probe_address, self.probe_data = '0' * self.addr_size, 0 self.probe_address, self.probe_data = '0' * self.addr_size, 0
self.set_corner(corner) self.set_corner(corner)
self.set_spice_constants() self.set_spice_constants()
@ -66,6 +87,7 @@ class functional(simulation):
self.num_cycles = cycles self.num_cycles = cycles
# This is to have ordered keys for random selection # This is to have ordered keys for random selection
self.stored_words = collections.OrderedDict() self.stored_words = collections.OrderedDict()
self.stored_spares = collections.OrderedDict()
self.read_check = [] self.read_check = []
self.read_results = [] self.read_results = []
@ -121,10 +143,12 @@ class functional(simulation):
# 1. Write all the write ports first to seed a bunch of locations. # 1. Write all the write ports first to seed a bunch of locations.
for port in self.write_ports: for port in self.write_ports:
addr = self.gen_addr() addr = self.gen_addr()
word = self.gen_data() (word, spare) = self.gen_data()
comment = self.gen_cycle_comment("write", word, addr, "1" * self.num_wmasks, port, self.t_current) combined_word = "{}+{}".format(word, spare)
self.add_write_one_port(comment, addr, word, "1" * self.num_wmasks, port) comment = self.gen_cycle_comment("write", combined_word, addr, "1" * self.num_wmasks, port, self.t_current)
self.add_write_one_port(comment, addr, word + spare, "1" * self.num_wmasks, port)
self.stored_words[addr] = word self.stored_words[addr] = word
self.stored_spares[addr[:self.addr_spare_index]] = spare
# All other read-only ports are noops. # All other read-only ports are noops.
for port in self.read_ports: for port in self.read_ports:
@ -142,7 +166,9 @@ class functional(simulation):
if port in self.write_ports: if port in self.write_ports:
self.add_noop_one_port(port) self.add_noop_one_port(port)
else: else:
comment = self.gen_cycle_comment("read", word, addr, "0" * self.num_wmasks, port, self.t_current) (addr, word, spare) = self.get_data()
combined_word = "{}+{}".format(word, spare)
comment = self.gen_cycle_comment("read", combined_word, addr, "0" * self.num_wmasks, port, self.t_current)
self.add_read_one_port(comment, addr, port) self.add_read_one_port(comment, addr, port)
self.add_read_check(word, port) self.add_read_check(word, port)
self.cycle_times.append(self.t_current) self.cycle_times.append(self.t_current)
@ -170,27 +196,33 @@ class functional(simulation):
if addr in w_addrs: if addr in w_addrs:
self.add_noop_one_port(port) self.add_noop_one_port(port)
else: else:
word = self.gen_data() (word, spare) = self.gen_data()
comment = self.gen_cycle_comment("write", word, addr, "1" * self.num_wmasks, port, self.t_current) combined_word = "{}+{}".format(word, spare)
self.add_write_one_port(comment, addr, word, "1" * self.num_wmasks, port) comment = self.gen_cycle_comment("write", combined_word, addr, "1" * self.num_wmasks, port, self.t_current)
self.add_write_one_port(comment, addr, word + spare, "1" * self.num_wmasks, port)
self.stored_words[addr] = word self.stored_words[addr] = word
self.stored_spares[addr[:self.addr_spare_index]] = spare
w_addrs.append(addr) w_addrs.append(addr)
elif op == "partial_write": elif op == "partial_write":
# write only to a word that's been written to # write only to a word that's been written to
(addr, old_word) = self.get_data() (addr, old_word, old_spare) = self.get_data()
# two ports cannot write to the same address # two ports cannot write to the same address
if addr in w_addrs: if addr in w_addrs:
self.add_noop_one_port(port) self.add_noop_one_port(port)
else: else:
word = self.gen_data() (word, spare) = self.gen_data()
wmask = self.gen_wmask() wmask = self.gen_wmask()
new_word = self.gen_masked_data(old_word, word, wmask) new_word = self.gen_masked_data(old_word, word, wmask)
comment = self.gen_cycle_comment("partial_write", word, addr, wmask, port, self.t_current) combined_word = "{}+{}".format(word, spare)
self.add_write_one_port(comment, addr, word, wmask, port) comment = self.gen_cycle_comment("partial_write", combined_word, addr, wmask, port, self.t_current)
self.add_write_one_port(comment, addr, word + spare, wmask, port)
self.stored_words[addr] = new_word self.stored_words[addr] = new_word
self.stored_spares[addr[:self.addr_spare_index]] = spare
w_addrs.append(addr) w_addrs.append(addr)
else: else:
(addr, word) = random.choice(list(self.stored_words.items())) (addr, word) = random.choice(list(self.stored_words.items()))
spare = self.stored_spares[addr[:self.addr_spare_index]]
combined_word = "{}+{}".format(word, spare)
# The write driver is not sized sufficiently to drive through the two # The write driver is not sized sufficiently to drive through the two
# bitcell access transistors to the read port. So, for now, we do not allow # bitcell access transistors to the read port. So, for now, we do not allow
# a simultaneous write and read to the same address on different ports. This # a simultaneous write and read to the same address on different ports. This
@ -198,9 +230,9 @@ class functional(simulation):
if addr in w_addrs: if addr in w_addrs:
self.add_noop_one_port(port) self.add_noop_one_port(port)
else: else:
comment = self.gen_cycle_comment("read", word, addr, "0" * self.num_wmasks, port, self.t_current) comment = self.gen_cycle_comment("read", combined_word, addr, "0" * self.num_wmasks, port, self.t_current)
self.add_read_one_port(comment, addr, port) self.add_read_one_port(comment, addr, port)
self.add_read_check(word, port) self.add_read_check(word + spare, port)
self.cycle_times.append(self.t_current) self.cycle_times.append(self.t_current)
self.t_current += self.period self.t_current += self.period
@ -227,18 +259,18 @@ class functional(simulation):
def add_read_check(self, word, port): def add_read_check(self, word, port):
""" Add to the check array to ensure a read works. """ """ Add to the check array to ensure a read works. """
try: try:
self.check self.check_count
except: except:
self.check = 0 self.check_count = 0
self.read_check.append([word, "{0}{1}".format(self.dout_name, port), self.t_current + self.period, self.check]) self.read_check.append([word, "{0}{1}".format(self.dout_name, port), self.t_current + self.period, self.check_count])
self.check += 1 self.check_count += 1
def read_stim_results(self): def read_stim_results(self):
# Extract dout values from spice timing.lis # Extract dout values from spice timing.lis
for (word, dout_port, eo_period, check) in self.read_check: for (word, dout_port, eo_period, check_count) in self.read_check:
sp_read_value = "" sp_read_value = ""
for bit in range(self.word_size + self.num_spare_cols): for bit in range(self.word_size + self.num_spare_cols):
value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(), bit, check)) value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(), bit, check_count))
try: try:
value = float(value) value = float(value)
if value > self.v_high: if value > self.v_high:
@ -260,13 +292,13 @@ class functional(simulation):
return (0, error) return (0, error)
self.read_results.append([sp_read_value, dout_port, eo_period, check]) self.read_results.append([sp_read_value, dout_port, eo_period, check_count])
return (1, "SUCCESS") return (1, "SUCCESS")
def check_stim_results(self): def check_stim_results(self):
for i in range(len(self.read_check)): for i in range(len(self.read_check)):
if self.read_check[i][0] != self.read_results[i][0]: if self.read_check[i][0] != self.read_results[i][0]:
str = "FAILED: {0} value {1} does not match written value {2} read during cycle {3} at time {4}n" str = "FAILED: {0} read value {1} does not match written value {2} during cycle {3} at time {4}n"
error = str.format(self.read_results[i][1], error = str.format(self.read_results[i][1],
self.read_results[i][0], self.read_results[i][0],
self.read_check[i][0], self.read_check[i][0],
@ -300,22 +332,22 @@ class functional(simulation):
def gen_data(self): def gen_data(self):
""" Generates a random word to write. """ """ Generates a random word to write. """
if not self.num_spare_cols: random_value = random.randint(0, self.max_data)
random_value = random.randint(0, (2 ** self.word_size) - 1) data_bits = binary_repr(random_value, self.word_size)
if self.num_spare_cols>0:
random_value = random.randint(0, self.max_col_data)
spare_bits = binary_repr(random_value, self.num_spare_cols)
else: else:
random_value1 = random.randint(0, (2 ** self.word_size) - 1) spare_bits = ""
random_value2 = random.randint(0, (2 ** self.num_spare_cols) - 1) return data_bits, spare_bits
random_value = random_value1 + random_value2
data_bits = self.convert_to_bin(random_value, False)
return data_bits
def gen_addr(self): def gen_addr(self):
""" Generates a random address value to write to. """ """ Generates a random address value to write to. """
if self.num_spare_rows==0: if self.valid_addresses:
random_value = random.randint(0, (2 ** self.addr_size) - 1) random_value = random.sample(self.valid_addresses, 1)[0]
else: else:
random_value = random.randint(0, ((2 ** (self.addr_size - 1) - 1)) + (self.num_spare_rows * self.words_per_row)) random_value = random.randint(0, self.max_address)
addr_bits = self.convert_to_bin(random_value, True) addr_bits = binary_repr(random_value, self.addr_size)
return addr_bits return addr_bits
def get_data(self): def get_data(self):
@ -323,20 +355,8 @@ class functional(simulation):
# Used for write masks since they should be writing to previously written addresses # Used for write masks since they should be writing to previously written addresses
addr = random.choice(list(self.stored_words.keys())) addr = random.choice(list(self.stored_words.keys()))
word = self.stored_words[addr] word = self.stored_words[addr]
return (addr, word) spare = self.stored_spares[addr[:self.addr_spare_index]]
return (addr, word, spare)
def convert_to_bin(self, value, is_addr):
""" Converts addr & word to usable binary values. """
new_value = str.replace(bin(value), "0b", "")
if(is_addr):
expected_value = self.addr_size
else:
expected_value = self.word_size + self.num_spare_cols
for i in range(expected_value - len(new_value)):
new_value = "0" + new_value
# print("Binary Conversion: {} to {}".format(value, new_value))
return new_value
def write_functional_stimulus(self): def write_functional_stimulus(self):
""" Writes SPICE stimulus. """ """ Writes SPICE stimulus. """
@ -432,12 +452,21 @@ class functional(simulation):
# Generate dout value measurements # Generate dout value measurements
self.sf.write("\n * Generation of dout measurements\n") self.sf.write("\n * Generation of dout measurements\n")
for (word, dout_port, eo_period, check) in self.read_check: for (word, dout_port, eo_period, check) in self.read_check:
t_intital = eo_period - 0.01 * self.period t_initial = eo_period - 0.01 * self.period
t_final = eo_period + 0.01 * self.period t_final = eo_period + 0.01 * self.period
for bit in range(self.word_size + self.num_spare_cols): num_bits = self.word_size + self.num_spare_cols
self.stim.gen_meas_value(meas_name="V{0}_{1}ck{2}".format(dout_port, bit, check), for bit in range(num_bits):
dout="{0}_{1}".format(dout_port, bit), measure_name = "V{0}_{1}ck{2}".format(dout_port, bit, check)
t_intital=t_intital, signal_name = "{0}_{1}".format(dout_port, bit)
voltage_value = self.stim.get_voltage(word[num_bits - bit - 1])
self.stim.add_comment("* CHECK {0} {1} = {2} time = {3}".format(signal_name,
measure_name,
voltage_value,
eo_period))
self.stim.gen_meas_value(meas_name=measure_name,
dout=signal_name,
t_initial=t_initial,
t_final=t_final) t_final=t_final)
self.stim.write_control(self.cycle_times[-1] + self.period) self.stim.write_control(self.cycle_times[-1] + self.period)

View File

@ -5,9 +5,8 @@
# (acting for and on behalf of Oklahoma State University) # (acting for and on behalf of Oklahoma State University)
# All rights reserved. # All rights reserved.
# #
import os,sys,re import os
import debug import debug
import math
import datetime import datetime
from .setup_hold import * from .setup_hold import *
from .delay import * from .delay import *
@ -16,6 +15,7 @@ import tech
import numpy as np import numpy as np
from globals import OPTS from globals import OPTS
class lib: class lib:
""" lib file generation.""" """ lib file generation."""
@ -601,7 +601,6 @@ class lib:
from .elmore import elmore as model from .elmore import elmore as model
else: else:
debug.error("{} model not recognized. See options.py for available models.".format(OPTS.model_name)) debug.error("{} model not recognized. See options.py for available models.".format(OPTS.model_name))
import math
m = model(self.sram, self.sp_file, self.corner) m = model(self.sram, self.sp_file, self.corner)
char_results = m.get_lib_values(self.slews,self.loads) char_results = m.get_lib_values(self.slews,self.loads)
@ -834,4 +833,4 @@ class lib:
#FIXME: should be read_fall_power #FIXME: should be read_fall_power
datasheet.write("{0},{1},".format('write_fall_power_{}'.format(port), read0_power)) datasheet.write("{0},{1},".format('write_fall_power_{}'.format(port), read0_power))

View File

@ -82,7 +82,13 @@ class setup_hold():
""" """
self.sf.write("\n* Generation of the data and clk signals\n") self.sf.write("\n* Generation of the data and clk signals\n")
incorrect_value = self.stim.get_inverse_value(correct_value) if correct_value == 1:
incorrect_value = 0
elif correct_value == 0:
incorrect_value = 1
else:
debug.error("Invalid value {}".format(correct_value))
if mode=="HOLD": if mode=="HOLD":
init_value = incorrect_value init_value = incorrect_value
start_value = correct_value start_value = correct_value

View File

@ -27,7 +27,10 @@ class simulation():
self.num_spare_cols = 0 self.num_spare_cols = 0
else: else:
self.num_spare_cols = self.sram.num_spare_cols self.num_spare_cols = self.sram.num_spare_cols
self.sp_file = spfile if not spfile:
self.sp_file = OPTS.openram_temp + "sram.sp"
else:
self.sp_file = spfile
self.all_ports = self.sram.all_ports self.all_ports = self.sram.all_ports
self.readwrite_ports = self.sram.readwrite_ports self.readwrite_ports = self.sram.readwrite_ports

View File

@ -169,22 +169,14 @@ class stimuli():
def gen_constant(self, sig_name, v_val): def gen_constant(self, sig_name, v_val):
""" Generates a constant signal with reference voltage and the voltage value """ """ Generates a constant signal with reference voltage and the voltage value """
self.sf.write("V{0} {0} 0 DC {1}\n".format(sig_name, v_val)) self.sf.write("V{0} {0} 0 DC {1}\n".format(sig_name, v_val))
def get_inverse_voltage(self, value): def get_voltage(self, value):
if value > 0.5 * self.voltage: if value == "0" or value == 0:
return 0 return 0
elif value <= 0.5 * self.voltage: elif value == "1" or value == 1:
return self.voltage return self.voltage
else: else:
debug.error("Invalid value to get an inverse of: {0}".format(value)) debug.error("Invalid value to get a voltage of: {0}".format(value))
def get_inverse_value(self, value):
if value > 0.5:
return 0
elif value <= 0.5:
return 1
else:
debug.error("Invalid value to get an inverse of: {0}".format(value))
def gen_meas_delay(self, meas_name, trig_name, targ_name, trig_val, targ_val, trig_dir, targ_dir, trig_td, targ_td): def gen_meas_delay(self, meas_name, trig_name, targ_name, trig_val, targ_val, trig_dir, targ_dir, trig_td, targ_td):
""" Creates the .meas statement for the measurement of delay """ """ Creates the .meas statement for the measurement of delay """
@ -228,8 +220,8 @@ class stimuli():
t_initial, t_initial,
t_final)) t_final))
def gen_meas_value(self, meas_name, dout, t_intital, t_final): def gen_meas_value(self, meas_name, dout, t_initial, t_final):
measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name, dout, t_intital, t_final) measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name, dout, t_initial, t_final)
self.sf.write(measure_string) self.sf.write(measure_string)
def write_control(self, end_time, runlvl=4): def write_control(self, end_time, runlvl=4):
@ -254,7 +246,7 @@ class stimuli():
# which is more accurate, but slower than the default trapezoid method # which is more accurate, but slower than the default trapezoid method
# Do not remove this or it may not converge due to some "pa_00" nodes # Do not remove this or it may not converge due to some "pa_00" nodes
# unless you figure out what these are. # unless you figure out what these are.
self.sf.write(".OPTIONS POST=1 RELTOL={0} PROBE method=gear\n".format(reltol)) self.sf.write(".OPTIONS POST=1 RELTOL={0} PROBE method=gear ACCT\n".format(reltol))
elif OPTS.spice_name == "spectre": elif OPTS.spice_name == "spectre":
self.sf.write("simulator lang=spectre\n") self.sf.write("simulator lang=spectre\n")
if OPTS.use_pex: if OPTS.use_pex:
@ -310,6 +302,9 @@ class stimuli():
for item in list(includes): for item in list(includes):
self.sf.write(".include \"{0}\"\n".format(item)) self.sf.write(".include \"{0}\"\n".format(item))
def add_comment(self, msg):
self.sf.write(msg + "\n")
def write_supply(self): def write_supply(self):
""" Writes supply voltage statements """ """ Writes supply voltage statements """
gnd_node_name = "0" gnd_node_name = "0"
@ -360,6 +355,8 @@ class stimuli():
# -r {2}timing.raw # -r {2}timing.raw
ng_cfg = open("{}.spiceinit".format(OPTS.openram_temp), "w") ng_cfg = open("{}.spiceinit".format(OPTS.openram_temp), "w")
ng_cfg.write("set num_threads={}\n".format(OPTS.num_sim_threads)) ng_cfg.write("set num_threads={}\n".format(OPTS.num_sim_threads))
ng_cfg.write("set ngbehavior=hsa\n")
ng_cfg.write("set ng_nomodcheck\n")
ng_cfg.close() ng_cfg.close()
cmd = "{0} -b -o {2}timing.lis {1}".format(OPTS.spice_exe, cmd = "{0} -b -o {2}timing.lis {1}".format(OPTS.spice_exe,

View File

@ -9,6 +9,7 @@ import debug
from math import log,ceil from math import log,ceil
import re import re
class trim_spice(): class trim_spice():
""" """
A utility to trim redundant parts of an SRAM spice netlist. A utility to trim redundant parts of an SRAM spice netlist.
@ -29,7 +30,6 @@ class trim_spice():
for i in range(len(self.spice)): for i in range(len(self.spice)):
self.spice[i] = self.spice[i].rstrip(" \n") self.spice[i] = self.spice[i].rstrip(" \n")
self.sp_buffer = self.spice self.sp_buffer = self.spice
def set_configuration(self, banks, rows, columns, word_size): def set_configuration(self, banks, rows, columns, word_size):
@ -46,21 +46,23 @@ class trim_spice():
self.col_addr_size = int(log(self.words_per_row, 2)) self.col_addr_size = int(log(self.words_per_row, 2))
self.bank_addr_size = self.col_addr_size + self.row_addr_size self.bank_addr_size = self.col_addr_size + self.row_addr_size
self.addr_size = self.bank_addr_size + int(log(self.num_banks, 2)) self.addr_size = self.bank_addr_size + int(log(self.num_banks, 2))
def trim(self, address, data_bit): def trim(self, address, data_bit):
""" Reduce the spice netlist but KEEP the given bits at the """
address (and things that will add capacitive load!)""" Reduce the spice netlist but KEEP the given bits at the
address (and things that will add capacitive load!)
"""
# Always start fresh if we do multiple reductions # Always start fresh if we do multiple reductions
self.sp_buffer = self.spice self.sp_buffer = self.spice
# Split up the address and convert to an int # Split up the address and convert to an int
wl_address = int(address[self.col_addr_size:],2) wl_address = int(address[self.col_addr_size:], 2)
if self.col_addr_size>0: if self.col_addr_size > 0:
col_address = int(address[0:self.col_addr_size],2) col_address = int(address[0:self.col_addr_size], 2)
else: else:
col_address = 0 col_address = 0
# 1. Keep cells in the bitcell array based on WL and BL # 1. Keep cells in the bitcell array based on WL and BL
wl_name = "wl_{}".format(wl_address) wl_name = "wl_{}".format(wl_address)
bl_name = "bl_{}".format(int(self.words_per_row*data_bit + col_address)) bl_name = "bl_{}".format(int(self.words_per_row*data_bit + col_address))
@ -81,7 +83,6 @@ class trim_spice():
self.sp_buffer.insert(0, "* It should NOT be used for LVS!!") self.sp_buffer.insert(0, "* It should NOT be used for LVS!!")
self.sp_buffer.insert(0, "* WARNING: This is a TRIMMED NETLIST.") self.sp_buffer.insert(0, "* WARNING: This is a TRIMMED NETLIST.")
wl_regex = r"wl\d*_{}".format(wl_address) wl_regex = r"wl\d*_{}".format(wl_address)
bl_regex = r"bl\d*_{}".format(int(self.words_per_row*data_bit + col_address)) bl_regex = r"bl\d*_{}".format(int(self.words_per_row*data_bit + col_address))
self.remove_insts("bitcell_array",[wl_regex,bl_regex]) self.remove_insts("bitcell_array",[wl_regex,bl_regex])
@ -91,11 +92,11 @@ class trim_spice():
#self.remove_insts("sense_amp_array",[bl_regex]) #self.remove_insts("sense_amp_array",[bl_regex])
# 3. Keep column muxes basd on BL # 3. Keep column muxes basd on BL
self.remove_insts("column_mux_array",[bl_regex]) self.remove_insts("column_mux_array", [bl_regex])
# 4. Keep write driver based on DATA # 4. Keep write driver based on DATA
data_regex = r"data_{}".format(data_bit) data_regex = r"data_{}".format(data_bit)
self.remove_insts("write_driver_array",[data_regex]) self.remove_insts("write_driver_array", [data_regex])
# 5. Keep wordline driver based on WL # 5. Keep wordline driver based on WL
# Need to keep the gater too # Need to keep the gater too
@ -111,7 +112,6 @@ class trim_spice():
sp.write("\n".join(self.sp_buffer)) sp.write("\n".join(self.sp_buffer))
sp.close() sp.close()
def remove_insts(self, subckt_name, keep_inst_list): def remove_insts(self, subckt_name, keep_inst_list):
"""This will remove all of the instances in the list from the named """This will remove all of the instances in the list from the named
subckt that DO NOT contain a term in the list. It just does a subckt that DO NOT contain a term in the list. It just does a
@ -119,7 +119,7 @@ class trim_spice():
net connection, the instance name, anything.. net connection, the instance name, anything..
""" """
removed_insts = 0 removed_insts = 0
#Expects keep_inst_list are regex patterns. Compile them here. # Expects keep_inst_list are regex patterns. Compile them here.
compiled_patterns = [re.compile(pattern) for pattern in keep_inst_list] compiled_patterns = [re.compile(pattern) for pattern in keep_inst_list]
start_name = ".SUBCKT {}".format(subckt_name) start_name = ".SUBCKT {}".format(subckt_name)

View File

@ -43,7 +43,7 @@ def error(str, return_value=0):
if globals.OPTS.debug: if globals.OPTS.debug:
pdb.set_trace() pdb.set_trace()
assert return_value == 0 assert return_value == 0
@ -108,7 +108,20 @@ def info(lev, str):
print_raw("[{0}/{1}]: {2}".format(class_name, print_raw("[{0}/{1}]: {2}".format(class_name,
frm[0].f_code.co_name, str)) frm[0].f_code.co_name, str))
def archive():
from globals import OPTS
try:
OPENRAM_HOME = os.path.abspath(os.environ.get("OPENRAM_HOME"))
except:
error("$OPENRAM_HOME is not properly defined.", 1)
import shutil
zip_file = "{0}/{1}_{2}".format(OPENRAM_HOME, "fail_", os.getpid())
info(0, "Archiving failed files to {}.zip".format(zip_file))
shutil.make_archive(zip_file, 'zip', OPTS.openram_temp)
def bp(): def bp():
""" """
An empty function so you can set soft breakpoints in pdb. An empty function so you can set soft breakpoints in pdb.

View File

@ -22,7 +22,7 @@ import getpass
import subprocess import subprocess
VERSION = "1.1.13" VERSION = "1.1.15"
NAME = "OpenRAM v{}".format(VERSION) NAME = "OpenRAM v{}".format(VERSION)
USAGE = "openram.py [options] <config file>\nUse -h for help.\n" USAGE = "openram.py [options] <config file>\nUse -h for help.\n"
@ -61,8 +61,13 @@ def parse_args():
optparse.make_option("-j", "--threads", optparse.make_option("-j", "--threads",
action="store", action="store",
type="int", type="int",
help="Specify the number of threads (default: 2)", help="Specify the number of threads (default: 1)",
dest="num_threads"), dest="num_threads"),
optparse.make_option("-m", "--sim_threads",
action="store",
type="int",
help="Specify the number of spice simulation threads (default: 2)",
dest="num_sim_threads"),
optparse.make_option("-v", optparse.make_option("-v",
"--verbose", "--verbose",
action="count", action="count",
@ -381,6 +386,10 @@ def purge_temp():
""" Remove the temp directory. """ """ Remove the temp directory. """
debug.info(1, debug.info(1,
"Purging temp directory: {}".format(OPTS.openram_temp)) "Purging temp directory: {}".format(OPTS.openram_temp))
#import inspect
#s = inspect.stack()
#print("Purge {0} in dir {1}".format(s[3].filename, OPTS.openram_temp))
# This annoyingly means you have to re-cd into # This annoyingly means you have to re-cd into
# the directory each debug iteration # the directory each debug iteration
# shutil.rmtree(OPTS.openram_temp, ignore_errors=True) # shutil.rmtree(OPTS.openram_temp, ignore_errors=True)
@ -429,9 +438,15 @@ def setup_paths():
if "__pycache__" not in full_path: if "__pycache__" not in full_path:
sys.path.append("{0}".format(full_path)) sys.path.append("{0}".format(full_path))
# Use a unique temp subdirectory # Use a unique temp subdirectory if multithreaded
OPTS.openram_temp += "/openram_{0}_{1}_temp/".format(getpass.getuser(), if OPTS.num_threads > 1 or OPTS.openram_temp == "/tmp":
os.getpid())
# Make a unique subdir
tempdir = "/openram_{0}_{1}_temp".format(getpass.getuser(),
os.getpid())
# Only add the unique subdir one time
if tempdir not in OPTS.openram_temp:
OPTS.openram_temp += tempdir
if not OPTS.openram_temp.endswith('/'): if not OPTS.openram_temp.endswith('/'):
OPTS.openram_temp += "/" OPTS.openram_temp += "/"
@ -470,6 +485,12 @@ def init_paths():
except OSError as e: except OSError as e:
if e.errno == 17: # errno.EEXIST if e.errno == 17: # errno.EEXIST
os.chmod(OPTS.openram_temp, 0o750) os.chmod(OPTS.openram_temp, 0o750)
#import inspect
#s = inspect.stack()
#from pprint import pprint
#pprint(s)
#print("Test {0} in dir {1}".format(s[2].filename, OPTS.openram_temp))
# Don't delete the output dir, it may have other files! # Don't delete the output dir, it may have other files!
# make the directory if it doesn't exist # make the directory if it doesn't exist

View File

@ -329,13 +329,13 @@ class bank(design.design):
self.input_control_signals = [] self.input_control_signals = []
port_num = 0 port_num = 0
for port in range(OPTS.num_rw_ports): for port in range(OPTS.num_rw_ports):
self.input_control_signals.append(["s_en{}".format(port_num), "w_en{}".format(port_num), "p_en_bar{}".format(port_num), "wl_en{}".format(port_num)]) self.input_control_signals.append(["p_en_bar{}".format(port_num), "s_en{}".format(port_num), "w_en{}".format(port_num)])
port_num += 1 port_num += 1
for port in range(OPTS.num_w_ports): for port in range(OPTS.num_w_ports):
self.input_control_signals.append(["w_en{}".format(port_num), "p_en_bar{}".format(port_num), "wl_en{}".format(port_num)]) self.input_control_signals.append(["p_en_bar{}".format(port_num), "w_en{}".format(port_num)])
port_num += 1 port_num += 1
for port in range(OPTS.num_r_ports): for port in range(OPTS.num_r_ports):
self.input_control_signals.append(["s_en{}".format(port_num), "p_en_bar{}".format(port_num), "wl_en{}".format(port_num)]) self.input_control_signals.append(["p_en_bar{}".format(port_num), "s_en{}".format(port_num)])
port_num += 1 port_num += 1
# Number of control lines in the bus for each port # Number of control lines in the bus for each port
@ -530,13 +530,16 @@ class bank(design.design):
height=self.dff.height) height=self.dff.height)
elif self.col_addr_size == 2: elif self.col_addr_size == 2:
self.column_decoder = factory.create(module_type="hierarchical_predecode2x4", self.column_decoder = factory.create(module_type="hierarchical_predecode2x4",
column_decoder=True,
height=self.dff.height) height=self.dff.height)
elif self.col_addr_size == 3: elif self.col_addr_size == 3:
self.column_decoder = factory.create(module_type="hierarchical_predecode3x8", self.column_decoder = factory.create(module_type="hierarchical_predecode3x8",
column_decoder=True,
height=self.dff.height) height=self.dff.height)
elif self.col_addr_size == 4: elif self.col_addr_size == 4:
self.column_decoder = factory.create(module_type="hierarchical_predecode4x16", self.column_decoder = factory.create(module_type="hierarchical_predecode4x16",
column_decoder=True,
height=self.dff.height) height=self.dff.height)
else: else:
# No error checking before? # No error checking before?
@ -692,6 +695,8 @@ class bank(design.design):
make_pins=(self.num_banks==1), make_pins=(self.num_banks==1),
pitch=self.m3_pitch) pitch=self.m3_pitch)
self.copy_layout_pin(self.port_address_inst[0], "wl_en", self.prefix + "wl_en0")
# Port 1 # Port 1
if len(self.all_ports)==2: if len(self.all_ports)==2:
# The other control bus is routed up to two pitches above the bitcell array # The other control bus is routed up to two pitches above the bitcell array
@ -707,6 +712,8 @@ class bank(design.design):
make_pins=(self.num_banks==1), make_pins=(self.num_banks==1),
pitch=self.m3_pitch) pitch=self.m3_pitch)
self.copy_layout_pin(self.port_address_inst[1], "wl_en", self.prefix + "wl_en1")
def route_port_data_to_bitcell_array(self, port): def route_port_data_to_bitcell_array(self, port):
""" Routing of BL and BR between port data and bitcell array """ """ Routing of BL and BR between port data and bitcell array """
@ -1055,21 +1062,6 @@ class bank(design.design):
to_layer="m2", to_layer="m2",
offset=control_pos) offset=control_pos)
# clk to wordline_driver
control_signal = self.prefix + "wl_en{}".format(port)
if port % 2:
pin_pos = self.port_address_inst[port].get_pin("wl_en").uc()
control_y_offset = self.bus_pins[port][control_signal].by()
mid_pos = vector(pin_pos.x, control_y_offset + self.m1_pitch)
else:
pin_pos = self.port_address_inst[port].get_pin("wl_en").bc()
control_y_offset = self.bus_pins[port][control_signal].uy()
mid_pos = vector(pin_pos.x, control_y_offset - self.m1_pitch)
control_x_offset = self.bus_pins[port][control_signal].cx()
control_pos = vector(control_x_offset, mid_pos.y)
self.add_wire(self.m1_stack, [pin_pos, mid_pos, control_pos])
self.add_via_center(layers=self.m1_stack,
offset=control_pos)
def graph_exclude_precharge(self): def graph_exclude_precharge(self):
""" """

View File

@ -64,7 +64,11 @@ class bitcell_array(bitcell_base_array):
self.cell_inst[row, col]=self.add_inst(name=name, self.cell_inst[row, col]=self.add_inst(name=name,
mod=self.cell) mod=self.cell)
self.connect_inst(self.get_bitcell_pins(row, col)) self.connect_inst(self.get_bitcell_pins(row, col))
# If it is a "core" cell, it could be trimmed for sim time
if col>0 and col<self.column_size-1 and row>0 and row<self.row_size-1:
self.trim_insts.add(name)
def analytical_power(self, corner, load): def analytical_power(self, corner, load):
"""Power of Bitcell array and bitline in nW.""" """Power of Bitcell array and bitline in nW."""

View File

@ -333,21 +333,19 @@ class control_logic(design.design):
row += 1 row += 1
self.place_gated_clk_buf_row(row) self.place_gated_clk_buf_row(row)
row += 1 row += 1
self.place_wlen_row(row) if (self.port_type == "rw") or (self.port_type == "r"):
row += 1 self.place_sen_row(row)
row += 1
if (self.port_type == "rw") or (self.port_type == "w"): if (self.port_type == "rw") or (self.port_type == "w"):
self.place_wen_row(row) self.place_wen_row(row)
height = self.w_en_gate_inst.uy()
control_center_y = self.w_en_gate_inst.uy()
row += 1 row += 1
self.place_pen_row(row) self.place_pen_row(row)
row += 1 row += 1
if (self.port_type == "rw") or (self.port_type == "w"): if (self.port_type == "rw") or (self.port_type == "w"):
self.place_rbl_delay_row(row) self.place_rbl_delay_row(row)
row += 1 row += 1
if (self.port_type == "rw") or (self.port_type == "r"): self.place_wlen_row(row)
self.place_sen_row(row) row += 1
row += 1
self.place_delay(row) self.place_delay(row)
height = self.delay_inst.uy() height = self.delay_inst.uy()
control_center_y = self.delay_inst.by() control_center_y = self.delay_inst.by()

View File

@ -11,6 +11,7 @@ from sram_factory import factory
from vector import vector from vector import vector
import debug import debug
from numpy import cumsum from numpy import cumsum
from tech import layer_properties as layer_props
class global_bitcell_array(bitcell_base_array.bitcell_base_array): class global_bitcell_array(bitcell_base_array.bitcell_base_array):
@ -223,11 +224,20 @@ class global_bitcell_array(bitcell_base_array.bitcell_base_array):
new_name = "{0}_{1}".format(base_name, col + col_value) new_name = "{0}_{1}".format(base_name, col + col_value)
self.copy_layout_pin(inst, pin_name, new_name) self.copy_layout_pin(inst, pin_name, new_name)
# Add the global word lines
wl_layer = layer_props.global_bitcell_array.wordline_layer
for wl_name in self.local_mods[0].get_inputs(): for wl_name in self.local_mods[0].get_inputs():
for local_inst in self.local_insts:
wl_pin = local_inst.get_pin(wl_name)
self.add_via_stack_center(from_layer=wl_pin.layer,
to_layer=wl_layer,
offset=wl_pin.center())
left_pin = self.local_insts[0].get_pin(wl_name) left_pin = self.local_insts[0].get_pin(wl_name)
right_pin = self.local_insts[-1].get_pin(wl_name) right_pin = self.local_insts[-1].get_pin(wl_name)
self.add_layout_pin_segment_center(text=wl_name, self.add_layout_pin_segment_center(text=wl_name,
layer=left_pin.layer, layer=wl_layer,
start=left_pin.lc(), start=left_pin.lc(),
end=right_pin.rc()) end=right_pin.rc())

View File

@ -18,19 +18,17 @@ class hierarchical_predecode(design.design):
""" """
Pre 2x4 and 3x8 and TBD 4x16 decoder shared code. Pre 2x4 and 3x8 and TBD 4x16 decoder shared code.
""" """
def __init__(self, name, input_number, height=None): def __init__(self, name, input_number, column_decoder=False, height=None):
self.number_of_inputs = input_number self.number_of_inputs = input_number
b = factory.create(module_type=OPTS.bitcell) b = factory.create(module_type=OPTS.bitcell)
if not height: if not height:
self.cell_height = b.height self.cell_height = b.height
self.column_decoder = False
else: else:
self.cell_height = height self.cell_height = height
# If we are pitch matched to the bitcell, it's a predecoder
# otherwise it's a column decoder (out of pgates) self.column_decoder = column_decoder
self.column_decoder = (height != b.height)
self.number_of_outputs = int(math.pow(2, self.number_of_inputs)) self.number_of_outputs = int(math.pow(2, self.number_of_inputs))
super().__init__(name) super().__init__(name)
@ -87,8 +85,14 @@ class hierarchical_predecode(design.design):
self.bus_layer = layer_props.hierarchical_predecode.bus_layer self.bus_layer = layer_props.hierarchical_predecode.bus_layer
self.bus_directions = layer_props.hierarchical_predecode.bus_directions self.bus_directions = layer_props.hierarchical_predecode.bus_directions
self.bus_pitch = getattr(self, self.bus_layer + "_pitch") if self.column_decoder:
self.bus_space = layer_props.hierarchical_predecode.bus_space_factor * getattr(self, self.bus_layer + "_space") # Column decoders may be routed on M2/M3 if there's a write mask
self.bus_pitch = self.m3_pitch
self.bus_space = self.m3_space
else:
self.bus_pitch = getattr(self, self.bus_layer + "_pitch")
self.bus_space = getattr(self, self.bus_layer + "_space")
self.bus_space = layer_props.hierarchical_predecode.bus_space_factor * self.bus_space
self.input_layer = layer_props.hierarchical_predecode.input_layer self.input_layer = layer_props.hierarchical_predecode.input_layer
self.output_layer = layer_props.hierarchical_predecode.output_layer self.output_layer = layer_props.hierarchical_predecode.output_layer
self.output_layer_pitch = getattr(self, self.output_layer + "_pitch") self.output_layer_pitch = getattr(self, self.output_layer + "_pitch")
@ -114,7 +118,8 @@ class hierarchical_predecode(design.design):
self.input_rails = self.create_vertical_bus(layer=self.bus_layer, self.input_rails = self.create_vertical_bus(layer=self.bus_layer,
offset=offset, offset=offset,
names=input_names, names=input_names,
length=self.height - 2 * self.bus_pitch) length=self.height - 2 * self.bus_pitch,
pitch=self.bus_pitch)
invert_names = ["Abar_{}".format(x) for x in range(self.number_of_inputs)] invert_names = ["Abar_{}".format(x) for x in range(self.number_of_inputs)]
non_invert_names = ["A_{}".format(x) for x in range(self.number_of_inputs)] non_invert_names = ["A_{}".format(x) for x in range(self.number_of_inputs)]
@ -123,7 +128,8 @@ class hierarchical_predecode(design.design):
self.decode_rails = self.create_vertical_bus(layer=self.bus_layer, self.decode_rails = self.create_vertical_bus(layer=self.bus_layer,
offset=offset, offset=offset,
names=decode_names, names=decode_names,
length=self.height - 2 * self.bus_pitch) length=self.height - 2 * self.bus_pitch,
pitch=self.bus_pitch)
def create_input_inverters(self): def create_input_inverters(self):
""" Create the input inverters to invert input signals for the decode stage. """ """ Create the input inverters to invert input signals for the decode stage. """
@ -175,10 +181,12 @@ class hierarchical_predecode(design.design):
mirror=mirror) mirror=mirror)
def route(self): def route(self):
self.route_input_inverters() self.route_input_inverters()
self.route_output_inverters()
self.route_inputs_to_rails() self.route_inputs_to_rails()
self.route_and_to_rails() self.route_input_ands()
self.route_output_and() self.route_output_ands()
self.route_vdd_gnd() self.route_vdd_gnd()
def route_inputs_to_rails(self): def route_inputs_to_rails(self):
@ -210,7 +218,7 @@ class hierarchical_predecode(design.design):
to_layer=self.bus_layer, to_layer=self.bus_layer,
offset=[self.decode_rails[a_pin].cx(), y_offset]) offset=[self.decode_rails[a_pin].cx(), y_offset])
def route_output_and(self): def route_output_ands(self):
""" """
Route all conections of the outputs and gates Route all conections of the outputs and gates
""" """
@ -225,12 +233,40 @@ class hierarchical_predecode(design.design):
def route_input_inverters(self): def route_input_inverters(self):
""" """
Route all conections of the inputs inverters [Inputs, outputs, vdd, gnd] Route all conections of the inverter inputs
"""
for inv_num in range(self.number_of_inputs):
in_pin = "in_{}".format(inv_num)
# route input
pin = self.inv_inst[inv_num].get_pin("A")
inv_in_pos = pin.center()
in_pos = vector(self.input_rails[in_pin].cx(), inv_in_pos.y)
self.add_path(self.input_layer, [in_pos, inv_in_pos])
# Inverter input pin
self.add_via_stack_center(from_layer=pin.layer,
to_layer=self.input_layer,
offset=inv_in_pos)
# Input rail pin position
via=self.add_via_stack_center(from_layer=self.input_layer,
to_layer=self.bus_layer,
offset=in_pos,
directions=self.bus_directions)
# Create the input pin at this location on the rail
self.add_layout_pin_rect_center(text=in_pin,
layer=self.bus_layer,
offset=in_pos,
height=via.mod.second_layer_height,
width=via.mod.second_layer_width)
def route_output_inverters(self):
"""
Route all conections of the inverter outputs
""" """
for inv_num in range(self.number_of_inputs): for inv_num in range(self.number_of_inputs):
out_pin = "Abar_{}".format(inv_num) out_pin = "Abar_{}".format(inv_num)
in_pin = "in_{}".format(inv_num)
inv_out_pin = self.inv_inst[inv_num].get_pin("Z") inv_out_pin = self.inv_inst[inv_num].get_pin("Z")
# add output so that it is just below the vdd or gnd rail # add output so that it is just below the vdd or gnd rail
@ -250,31 +286,11 @@ class hierarchical_predecode(design.design):
offset=rail_pos, offset=rail_pos,
directions=self.bus_directions) directions=self.bus_directions)
# route input def route_input_ands(self):
pin = self.inv_inst[inv_num].get_pin("A") """
inv_in_pos = pin.center() Route the different permutations of the NAND/AND decocer cells.
in_pos = vector(self.input_rails[in_pin].cx(), inv_in_pos.y) """
self.add_path(self.input_layer, [in_pos, inv_in_pos])
self.add_via_stack_center(from_layer=pin.layer,
to_layer=self.input_layer,
offset=inv_in_pos)
via=self.add_via_stack_center(from_layer=self.input_layer,
to_layer=self.bus_layer,
offset=in_pos)
# Create the input pin at this location on the rail
self.add_layout_pin_rect_center(text=in_pin,
layer=self.bus_layer,
offset=in_pos,
height=via.mod.second_layer_height,
width=via.mod.second_layer_width)
# This is a hack to fix via-to-via spacing issues, but it is currently
# causing its own DRC problems.
# if layer_props.hierarchical_predecode.vertical_supply:
# below_rail = vector(self.decode_rails[out_pin].cx(), y_offset - (self.cell_height / 2))
# self.add_path(self.bus_layer, [rail_pos, below_rail], width=self.li_width + self.m1_enclose_mcon * 2)
def route_and_to_rails(self):
# This 2D array defines the connection mapping # This 2D array defines the connection mapping
and_input_line_combination = self.get_and_input_line_combination() and_input_line_combination = self.get_and_input_line_combination()
for k in range(self.number_of_outputs): for k in range(self.number_of_outputs):

View File

@ -13,8 +13,8 @@ class hierarchical_predecode2x4(hierarchical_predecode):
""" """
Pre 2x4 decoder used in hierarchical_decoder. Pre 2x4 decoder used in hierarchical_decoder.
""" """
def __init__(self, name, height=None): def __init__(self, name, column_decoder=False, height=None):
super().__init__( name, 2, height) super().__init__(name, 2, column_decoder, height)
self.create_netlist() self.create_netlist()
if not OPTS.netlist_only: if not OPTS.netlist_only:

View File

@ -13,8 +13,8 @@ class hierarchical_predecode3x8(hierarchical_predecode):
""" """
Pre 3x8 decoder used in hierarchical_decoder. Pre 3x8 decoder used in hierarchical_decoder.
""" """
def __init__(self, name, height=None): def __init__(self, name, column_decoder=False, height=None):
super().__init__(name, 3, height) super().__init__(name, 3, column_decoder, height)
self.create_netlist() self.create_netlist()
if not OPTS.netlist_only: if not OPTS.netlist_only:

View File

@ -13,8 +13,8 @@ class hierarchical_predecode4x16(hierarchical_predecode):
""" """
Pre 4x16 decoder used in hierarchical_decoder. Pre 4x16 decoder used in hierarchical_decoder.
""" """
def __init__(self, name, height=None): def __init__(self, name, column_decoder=False, height=None):
super().__init__(name, 4, height) super().__init__(name, 4, column_decoder, height)
self.create_netlist() self.create_netlist()
if not OPTS.netlist_only: if not OPTS.netlist_only:

View File

@ -10,6 +10,7 @@ from globals import OPTS
from sram_factory import factory from sram_factory import factory
from vector import vector from vector import vector
import debug import debug
from tech import layer_properties as layer_props
class local_bitcell_array(bitcell_base_array.bitcell_base_array): class local_bitcell_array(bitcell_base_array.bitcell_base_array):
@ -190,6 +191,11 @@ class local_bitcell_array(bitcell_base_array.bitcell_base_array):
def route(self): def route(self):
global_wl_layer = layer_props.global_bitcell_array.wordline_layer
global_wl_pitch = getattr(self, "{}_pitch".format(global_wl_layer))
global_wl_pitch_factor = layer_props.global_bitcell_array.wordline_pitch_factor
local_wl_layer = layer_props.local_bitcell_array.wordline_layer
# Route the global wordlines # Route the global wordlines
for port in self.all_ports: for port in self.all_ports:
if port == 0: if port == 0:
@ -204,25 +210,33 @@ class local_bitcell_array(bitcell_base_array.bitcell_base_array):
in_pin = self.wl_insts[port].get_pin(in_pin_name) in_pin = self.wl_insts[port].get_pin(in_pin_name)
y_offset = in_pin.cy() y_offset = in_pin.cy()
if port == 0: if port == 0:
y_offset -= 2 * self.m3_pitch y_offset -= global_wl_pitch_factor * global_wl_pitch
else: else:
y_offset += 2 * self.m3_pitch y_offset += global_wl_pitch_factor * global_wl_pitch
self.add_layout_pin_segment_center(text=wl_name,
layer="m3",
start=vector(self.wl_insts[port].lx(), y_offset),
end=vector(self.wl_insts[port].lx() + self.wl_array.width, y_offset))
mid = vector(in_pin.cx(), y_offset) mid = vector(in_pin.cx(), y_offset)
self.add_path("m2", [in_pin.center(), mid])
self.add_layout_pin_rect_center(text=wl_name,
layer=global_wl_layer,
offset=mid)
self.add_path(local_wl_layer, [in_pin.center(), mid])
# A short jog to the global line
self.add_via_stack_center(from_layer=in_pin.layer, self.add_via_stack_center(from_layer=in_pin.layer,
to_layer="m2", to_layer=local_wl_layer,
offset=in_pin.center()) offset=in_pin.center(),
self.add_via_center(self.m2_stack, min_area=True)
offset=mid) self.add_path(local_wl_layer, [in_pin.center(), mid])
self.add_via_stack_center(from_layer=local_wl_layer,
to_layer=global_wl_layer,
offset=mid,
min_area=True)
# Add the global WL pin
self.add_layout_pin_rect_center(text=wl_name,
layer=global_wl_layer,
offset=mid)
# Route the buffers # Route the buffers
for port in self.all_ports: for port in self.all_ports:
driver_outputs = self.driver_wordline_outputs[port] driver_outputs = self.driver_wordline_outputs[port]
@ -239,7 +253,12 @@ class local_bitcell_array(bitcell_base_array.bitcell_base_array):
out_loc = out_pin.lc() out_loc = out_pin.lc()
mid_loc = vector(self.wl_insts[port].lx() - 1.5 * self.m3_pitch, out_loc.y) mid_loc = vector(self.wl_insts[port].lx() - 1.5 * self.m3_pitch, out_loc.y)
in_loc = in_pin.rc() in_loc = in_pin.rc()
self.add_path(out_pin.layer, [out_loc, mid_loc, in_loc])
self.add_path(out_pin.layer, [out_loc, mid_loc])
self.add_via_stack_center(from_layer=out_pin.layer,
to_layer=in_pin.layer,
offset=mid_loc)
self.add_path(in_pin.layer, [mid_loc, in_loc])
def get_main_array_top(self): def get_main_array_top(self):
return self.bitcell_array_inst.by() + self.bitcell_array.get_main_array_top() return self.bitcell_array_inst.by() + self.bitcell_array.get_main_array_top()

View File

@ -104,7 +104,7 @@ class options(optparse.Values):
# This determines whether LVS and DRC is checked for every submodule. # This determines whether LVS and DRC is checked for every submodule.
inline_lvsdrc = False inline_lvsdrc = False
# Remove noncritical memory cells for characterization speed-up # Remove noncritical memory cells for characterization speed-up
trim_netlist = False trim_netlist = True
# Run with extracted parasitics # Run with extracted parasitics
use_pex = False use_pex = False
# Output config with all options # Output config with all options
@ -135,7 +135,7 @@ class options(optparse.Values):
magic_exe = None magic_exe = None
# Number of threads to use # Number of threads to use
num_threads = 2 num_threads = 1
# Number of threads to use in ngspice/hspice # Number of threads to use in ngspice/hspice
num_sim_threads = 2 num_sim_threads = 2
@ -155,6 +155,9 @@ class options(optparse.Values):
# Route the input/output pins to the perimeter # Route the input/output pins to the perimeter
perimeter_pins = True perimeter_pins = True
# Detailed or abstract LEF view
detailed_lef = False
keep_temp = False keep_temp = False

View File

@ -87,13 +87,11 @@ class pdriver(pgate.pgate):
def add_modules(self): def add_modules(self):
self.inv_list = [] self.inv_list = []
add_well = self.add_wells
for size in self.size_list: for size in self.size_list:
temp_inv = factory.create(module_type="pinv", temp_inv = factory.create(module_type="pinv",
size=size, size=size,
height=self.height, height=self.height,
add_wells=add_well) add_wells=self.add_wells)
add_well=False
self.inv_list.append(temp_inv) self.inv_list.append(temp_inv)
self.add_mod(temp_inv) self.add_mod(temp_inv)

View File

@ -148,8 +148,9 @@ class pin_group:
enclosure = self.router.compute_pin_enclosure(ll, ur, ll.z) enclosure = self.router.compute_pin_enclosure(ll, ur, ll.z)
pin_list.append(enclosure) pin_list.append(enclosure)
debug.check(len(pin_list) > 0, if len(pin_list) == 0:
"Did not find any enclosures.") debug.error("Did not find any enclosures for {}".format(self.name))
self.router.write_debug_gds("pin_enclosure_error.gds")
# Now simplify the enclosure list # Now simplify the enclosure list
new_pin_list = self.remove_redundant_shapes(pin_list) new_pin_list = self.remove_redundant_shapes(pin_list)

View File

@ -28,7 +28,7 @@ class router(router_tech):
route on a given layer. This is limited to two layer routes. route on a given layer. This is limited to two layer routes.
It populates blockages on a grid class. It populates blockages on a grid class.
""" """
def __init__(self, layers, design, gds_filename=None, bbox=None, route_track_width=1): def __init__(self, layers, design, gds_filename=None, bbox=None, margin=0, route_track_width=1):
""" """
This will instantiate a copy of the gds file or the module at (0,0) and This will instantiate a copy of the gds file or the module at (0,0) and
route on top of this. The blockages from the gds/module will be route on top of this. The blockages from the gds/module will be
@ -83,9 +83,11 @@ class router(router_tech):
# A list of path blockages (they might be expanded for wide metal DRC) # A list of path blockages (they might be expanded for wide metal DRC)
self.path_blockages = [] self.path_blockages = []
self.init_bbox(bbox) # The perimeter pins should be placed outside the SRAM macro by a distance
self.margin = margin
self.init_bbox(bbox, margin)
def init_bbox(self, bbox=None): def init_bbox(self, bbox=None, margin=0):
""" """
Initialize the ll,ur values with the paramter or using the layout boundary. Initialize the ll,ur values with the paramter or using the layout boundary.
""" """
@ -99,18 +101,19 @@ class router(router_tech):
else: else:
self.ll, self.ur = bbox self.ll, self.ur = bbox
self.bbox = (self.ll, self.ur) margin_offset = vector(margin, margin)
self.bbox = (self.ll - margin_offset, self.ur + margin_offset)
size = self.ur - self.ll size = self.ur - self.ll
debug.info(1, "Size: {0} x {1}".format(size.x, size.y)) debug.info(1, "Size: {0} x {1} with perimeter margin {2}".format(size.x, size.y, margin))
def get_bbox(self): def get_bbox(self):
return self.bbox return self.bbox
def create_routing_grid(self, router_type, bbox=None): def create_routing_grid(self, router_type):
""" """
Create a sprase routing grid with A* expansion functions. Create a sprase routing grid with A* expansion functions.
""" """
self.init_bbox(bbox) self.init_bbox(self.bbox, self.margin)
self.rg = router_type(self.ll, self.ur, self.track_width) self.rg = router_type(self.ll, self.ur, self.track_width)
def clear_pins(self): def clear_pins(self):
@ -1212,8 +1215,9 @@ class router(router_tech):
return None return None
def get_pin(self, pin_name): def get_ll_pin(self, pin_name):
""" Return the lowest, leftest pin group """ """ Return the lowest, leftest pin group """
keep_pin = None keep_pin = None
for index,pg in enumerate(self.pin_groups[pin_name]): for index,pg in enumerate(self.pin_groups[pin_name]):
for pin in pg.enclosures: for pin in pg.enclosures:

View File

@ -17,12 +17,17 @@ class signal_escape_router(router):
A router that routes signals to perimeter and makes pins. A router that routes signals to perimeter and makes pins.
""" """
def __init__(self, layers, design, bbox=None, gds_filename=None): def __init__(self, layers, design, bbox=None, margin=0, gds_filename=None):
""" """
This will route on layers in design. It will get the blockages from This will route on layers in design. It will get the blockages from
either the gds file name or the design itself (by saving to a gds file). either the gds file name or the design itself (by saving to a gds file).
""" """
router.__init__(self, layers, design, gds_filename, bbox) router.__init__(self,
layers=layers,
design=design,
gds_filename=gds_filename,
bbox=bbox,
margin=margin)
def perimeter_dist(self, pin_name): def perimeter_dist(self, pin_name):
""" """
@ -54,8 +59,8 @@ class signal_escape_router(router):
start_time = datetime.now() start_time = datetime.now()
for pin_name in ordered_pin_names: for pin_name in ordered_pin_names:
self.route_signal(pin_name) self.route_signal(pin_name)
#if pin_name == "dout1[1]": # if pin_name == "dout0[1]":
# self.write_debug_gds("postroute.gds", False) # self.write_debug_gds("postroute.gds", True)
print_time("Maze routing pins",datetime.now(), start_time, 3) print_time("Maze routing pins",datetime.now(), start_time, 3)

View File

@ -79,8 +79,8 @@ class supply_tree_router(router):
""" """
remaining_components = sum(not x.is_routed() for x in self.pin_groups[pin_name]) remaining_components = sum(not x.is_routed() for x in self.pin_groups[pin_name])
debug.info(1, "Routing {0} with {1} pin components to connect.".format(pin_name, debug.info(1, "Routing {0} with {1} pins.".format(pin_name,
remaining_components)) remaining_components))
# Create full graph # Create full graph
debug.info(2, "Creating adjacency matrix") debug.info(2, "Creating adjacency matrix")
@ -108,7 +108,9 @@ class supply_tree_router(router):
connections.append((x, y)) connections.append((x, y))
# Route MST components # Route MST components
for (src, dest) in connections: for index, (src, dest) in enumerate(connections):
if not (index % 100):
debug.info(1, "{0} supply segments routed, {1} remaining.".format(index, len(connections) - index))
self.route_signal(pin_name, src, dest) self.route_signal(pin_name, src, dest)
# if pin_name == "gnd": # if pin_name == "gnd":
# print("\nSRC {}: ".format(src) + str(self.pin_groups[pin_name][src].grids) + str(self.pin_groups[pin_name][src].blockages)) # print("\nSRC {}: ".format(src) + str(self.pin_groups[pin_name][src].grids) + str(self.pin_groups[pin_name][src].blockages))

View File

@ -57,11 +57,8 @@ class sram():
if not OPTS.is_unit_test: if not OPTS.is_unit_test:
print_time("SRAM creation", datetime.datetime.now(), start_time) print_time("SRAM creation", datetime.datetime.now(), start_time)
def sp_write(self, name): def sp_write(self, name, lvs=False, trim=False):
self.s.sp_write(name) self.s.sp_write(name, lvs, trim)
def lvs_write(self, name):
self.s.lvs_write(name)
def lef_write(self, name): def lef_write(self, name):
self.s.lef_write(name) self.s.lef_write(name)
@ -123,7 +120,7 @@ class sram():
start_time = datetime.datetime.now() start_time = datetime.datetime.now()
lvsname = OPTS.output_path + self.s.name + ".lvs.sp" lvsname = OPTS.output_path + self.s.name + ".lvs.sp"
debug.print_raw("LVS: Writing to {0}".format(lvsname)) debug.print_raw("LVS: Writing to {0}".format(lvsname))
self.lvs_write(lvsname) self.sp_write(lvsname, lvs=True)
if not OPTS.netlist_only and OPTS.check_lvsdrc: if not OPTS.netlist_only and OPTS.check_lvsdrc:
verify.write_lvs_script(cell_name=self.s.name, verify.write_lvs_script(cell_name=self.s.name,
gds_name=os.path.basename(gdsname), gds_name=os.path.basename(gdsname),

View File

@ -340,6 +340,15 @@ class sram_1bank(sram_base):
def route_dff(self, port, add_routes): def route_dff(self, port, add_routes):
# This is only done when we add_routes because the data channel will be larger
# so that can be used for area estimation.
if add_routes:
self.route_col_addr_dffs(port)
self.route_data_dffs(port, add_routes)
def route_col_addr_dffs(self, port):
route_map = [] route_map = []
# column mux dff is routed on it's own since it is to the far end # column mux dff is routed on it's own since it is to the far end
@ -351,6 +360,38 @@ class sram_1bank(sram_base):
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
route_map.extend(list(zip(bank_pins, dff_pins))) route_map.extend(list(zip(bank_pins, dff_pins)))
if len(route_map) > 0:
layer_stack = self.m1_stack
if port == 0:
offset = vector(self.control_logic_insts[port].rx() + self.dff.width,
- self.data_bus_size[port] + 2 * self.m3_pitch)
cr = channel_route(netlist=route_map,
offset=offset,
layer_stack=layer_stack,
parent=self)
# This causes problem in magic since it sometimes cannot extract connectivity of isntances
# with no active devices.
self.add_inst(cr.name, cr)
self.connect_inst([])
#self.add_flat_inst(cr.name, cr)
else:
offset = vector(0,
self.bank.height + self.m3_pitch)
cr = channel_route(netlist=route_map,
offset=offset,
layer_stack=layer_stack,
parent=self)
# This causes problem in magic since it sometimes cannot extract connectivity of isntances
# with no active devices.
self.add_inst(cr.name, cr)
self.connect_inst([])
#self.add_flat_inst(cr.name, cr)
def route_data_dffs(self, port, add_routes):
route_map = []
# wmask dff # wmask dff
if self.num_wmasks > 0 and port in self.write_ports: if self.num_wmasks > 0 and port in self.write_ports:
dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)] dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)]
@ -377,6 +418,7 @@ class sram_1bank(sram_base):
if len(route_map) > 0: if len(route_map) > 0:
# The write masks will have blockages on M1
if self.num_wmasks > 0 and port in self.write_ports: if self.num_wmasks > 0 and port in self.write_ports:
layer_stack = self.m3_stack layer_stack = self.m3_stack
else: else:

View File

@ -196,7 +196,7 @@ class sram_base(design, verilog, lef):
self.add_lvs_correspondence_points() self.add_lvs_correspondence_points()
# self.offset_all_coordinates() self.offset_all_coordinates()
highest_coord = self.find_highest_coords() highest_coord = self.find_highest_coords()
self.width = highest_coord[0] self.width = highest_coord[0]
@ -248,22 +248,33 @@ class sram_base(design, verilog, lef):
# Find the lowest leftest pin for vdd and gnd # Find the lowest leftest pin for vdd and gnd
for pin_name in ["vdd", "gnd"]: for pin_name in ["vdd", "gnd"]:
# Copy the pin shape to rectangles # Copy the pin shape(s) to rectangles
for pin in self.get_pins(pin_name): for pin in self.get_pins(pin_name):
self.add_rect(pin.layer, self.add_rect(pin.layer,
pin.ll(), pin.ll(),
pin.width(), pin.width(),
pin.height()) pin.height())
# Remove the pins
# Remove the pin shape(s)
self.remove_layout_pin(pin_name) self.remove_layout_pin(pin_name)
pin = rtr.get_pin(pin_name) # Get the lowest, leftest pin
pin = rtr.get_ll_pin(pin_name)
# Add it as an IO pin to the perimeter
lowest_coord = self.find_lowest_coords()
route_width = pin.rx() - lowest_coord.x
pin_width = 2 * getattr(self, "{}_width".format(pin.layer))
pin_offset = vector(lowest_coord.x, pin.by())
self.add_layout_pin(pin_name, self.add_layout_pin(pin_name,
pin.layer, pin.layer,
pin.ll(), pin_offset,
pin.width(), pin_width,
pin.height()) pin.height())
self.add_rect(pin.layer,
pin_offset,
route_width,
pin.height())
def route_escape_pins(self): def route_escape_pins(self):
""" """
@ -306,7 +317,9 @@ class sram_base(design, verilog, lef):
pins_to_route.append("spare_wen{0}[{1}]".format(port, bit)) pins_to_route.append("spare_wen{0}[{1}]".format(port, bit))
from signal_escape_router import signal_escape_router as router from signal_escape_router import signal_escape_router as router
rtr=router(self.m3_stack, self) rtr=router(layers=self.m3_stack,
design=self,
margin=4 * self.m3_pitch)
rtr.escape_route(pins_to_route) rtr.escape_route(pins_to_route)
def compute_bus_sizes(self): def compute_bus_sizes(self):
@ -675,7 +688,7 @@ class sram_base(design, verilog, lef):
return insts return insts
def sp_write(self, sp_name, lvs_netlist=False): def sp_write(self, sp_name, lvs=False, trim=False):
# Write the entire spice of the object to the file # Write the entire spice of the object to the file
############################################################ ############################################################
# Spice circuit # Spice circuit
@ -688,6 +701,8 @@ class sram_base(design, verilog, lef):
sp.write("* Data bits: {}\n".format(self.word_size)) sp.write("* Data bits: {}\n".format(self.word_size))
sp.write("* Banks: {}\n".format(self.num_banks)) sp.write("* Banks: {}\n".format(self.num_banks))
sp.write("* Column mux: {}:1\n".format(self.words_per_row)) sp.write("* Column mux: {}:1\n".format(self.words_per_row))
sp.write("* Trimmed: {}\n".format(trim))
sp.write("* LVS: {}\n".format(lvs))
sp.write("**************************************************\n") sp.write("**************************************************\n")
# This causes unit test mismatch # This causes unit test mismatch
@ -696,13 +711,10 @@ class sram_base(design, verilog, lef):
# sp.write(".global {0} {1}\n".format(spice["vdd_name"], # sp.write(".global {0} {1}\n".format(spice["vdd_name"],
# spice["gnd_name"])) # spice["gnd_name"]))
usedMODS = list() usedMODS = list()
self.sp_write_file(sp, usedMODS, lvs_netlist=lvs_netlist) self.sp_write_file(sp, usedMODS, lvs=lvs, trim=trim)
del usedMODS del usedMODS
sp.close() sp.close()
def lvs_write(self, sp_name):
self.sp_write(sp_name, lvs_netlist=True)
def graph_exclude_bits(self, targ_row, targ_col): def graph_exclude_bits(self, targ_row, targ_col):
""" """
Excludes bits in column from being added to graph except target Excludes bits in column from being added to graph except target

View File

@ -0,0 +1,56 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2021 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import unittest
from testutils import *
import sys, os
sys.path.append(os.getenv("OPENRAM_HOME"))
import globals
from globals import OPTS
from sram_factory import factory
import debug
class sram_1bank_4mux_1rw_1r_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
from sram_config import sram_config
OPTS.num_rw_ports = 1
OPTS.num_r_ports = 1
OPTS.num_w_ports = 0
globals.setup_bitcell()
c = sram_config(word_size=4,
num_words=64,
num_banks=1)
c.words_per_row=4
c.recompute_sizes()
debug.info(1, "Layout test for {}rw,{}r,{}w sram "
"with {} bit words, {} words, {} words per "
"row, {} banks".format(OPTS.num_rw_ports,
OPTS.num_r_ports,
OPTS.num_w_ports,
c.word_size,
c.num_words,
c.words_per_row,
c.num_banks))
a = factory.create(module_type="sram", sram_config=c)
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(testRunner=debugTestRunner())

View File

@ -30,19 +30,13 @@ class timing_sram_test(openram_test):
reload(characterizer) reload(characterizer)
from characterizer import delay from characterizer import delay
from sram_config import sram_config from sram_config import sram_config
c = sram_config(word_size=1, c = sram_config(word_size=4,
num_words=16, num_words=16,
num_banks=1) num_banks=1)
c.words_per_row=1 c.words_per_row=1
# c = sram_config(word_size=32,
# num_words=256,
# num_banks=1)
# c.words_per_row=2
c.recompute_sizes() c.recompute_sizes()
debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank") debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank")
s = factory.create(module_type="sram", sram_config=c) s = factory.create(module_type="sram", sram_config=c)
#import sys
#sys.exit(1)
tempspice = OPTS.openram_temp + "temp.sp" tempspice = OPTS.openram_temp + "temp.sp"
s.sp_write(tempspice) s.sp_write(tempspice)
@ -61,34 +55,35 @@ class timing_sram_test(openram_test):
data.update(port_data[0]) data.update(port_data[0])
if OPTS.tech_name == "freepdk45": if OPTS.tech_name == "freepdk45":
golden_data = {'min_period': 0.898, golden_data = {'delay_hl': [0.23941909999999997],
'write1_power': [0.2659137999999999], 'delay_lh': [0.23941909999999997],
'disabled_write0_power': [0.1782495], 'disabled_read0_power': [0.18183159999999998],
'disabled_read0_power': [0.14490679999999997], 'disabled_read1_power': [0.1979447],
'write0_power': [0.3330119], 'disabled_write0_power': [0.2129604],
'disabled_write1_power': [0.1865223], 'disabled_write1_power': [0.23266849999999997],
'leakage_power': 0.0014532, 'leakage_power': 0.0019882,
'disabled_read1_power': [0.1627516], 'min_period': 0.938,
'slew_lh': [0.25367799999999996], 'read0_power': [0.4115467],
'slew_hl': [0.25367799999999996], 'read1_power': [0.41158859999999997],
'delay_lh': [0.23820930000000004], 'slew_hl': [0.2798571],
'delay_hl': [0.23820930000000004], 'slew_lh': [0.2798571],
'read1_power': [0.3005756], 'write0_power': [0.45873749999999996],
'read0_power': [0.3005888]} 'write1_power': [0.40716199999999997]}
elif OPTS.tech_name == "scn4m_subm": elif OPTS.tech_name == "scn4m_subm":
golden_data = {'leakage_power': 0.0006356576000000001, golden_data = {'delay_hl': [1.7652000000000003],
'write1_power': [11.292700000000002], 'delay_lh': [1.7652000000000003],
'read0_power': [12.98], 'disabled_read0_power': [8.2716],
'disabled_write1_power': [8.3707], 'disabled_read1_power': [9.5857],
'write0_power': [14.4447], 'delay_hl': [1.7445000000000002], 'disabled_write0_power': [9.9825],
'disabled_read0_power': [6.4325], 'disabled_write1_power': [10.598400000000002],
'slew_hl': [1.7437], 'leakage_power': 0.0006681718,
'disabled_write0_power': [8.1307], 'min_period': 6.562,
'slew_lh': [1.7437], 'read0_power': [18.6446],
'read1_power': [12.9869], 'read1_power': [18.5126],
'disabled_read1_power': [7.706], 'slew_hl': [1.9026],
'min_period': 6.25, 'slew_lh': [1.9026],
'delay_lh': [1.7445000000000002]} 'write0_power': [21.022600000000004],
'write1_power': [16.6377]}
else: else:
self.assertTrue(False) # other techs fail self.assertTrue(False) # other techs fail
# Check if no too many or too few results # Check if no too many or too few results

View File

@ -12,8 +12,7 @@ import sys, os
sys.path.append(os.getenv("OPENRAM_HOME")) sys.path.append(os.getenv("OPENRAM_HOME"))
import globals import globals
from globals import OPTS from globals import OPTS
from sram_factory import factory
import debug
class timing_setup_test(openram_test): class timing_setup_test(openram_test):
@ -29,14 +28,12 @@ class timing_setup_test(openram_test):
import characterizer import characterizer
reload(characterizer) reload(characterizer)
from characterizer import setup_hold from characterizer import setup_hold
import sram
import tech import tech
slews = [tech.spice["rise_time"]*2] slews = [tech.spice["rise_time"]*2]
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
sh = setup_hold(corner) sh = setup_hold(corner)
data = sh.analyze(slews,slews) data = sh.analyze(slews,slews)
#print data
if OPTS.tech_name == "freepdk45": if OPTS.tech_name == "freepdk45":
golden_data = {'hold_times_HL': [-0.0158691], golden_data = {'hold_times_HL': [-0.0158691],
'hold_times_LH': [-0.0158691], 'hold_times_LH': [-0.0158691],
@ -47,6 +44,11 @@ class timing_setup_test(openram_test):
'hold_times_LH': [-0.11718749999999999], 'hold_times_LH': [-0.11718749999999999],
'setup_times_HL': [0.16357419999999998], 'setup_times_HL': [0.16357419999999998],
'setup_times_LH': [0.1757812]} 'setup_times_LH': [0.1757812]}
elif OPTS.tech_name == "sky130":
golden_data = {'hold_times_HL': [-0.05615234],
'hold_times_LH': [-0.03173828],
'setup_times_HL': [0.078125],
'setup_times_LH': [0.1025391]}
else: else:
self.assertTrue(False) # other techs fail self.assertTrue(False) # other techs fail

View File

@ -30,7 +30,7 @@ class timing_sram_test(openram_test):
reload(characterizer) reload(characterizer)
from characterizer import delay from characterizer import delay
from sram_config import sram_config from sram_config import sram_config
c = sram_config(word_size=1, c = sram_config(word_size=4,
num_words=16, num_words=16,
num_banks=1) num_banks=1)
c.words_per_row=1 c.words_per_row=1
@ -55,35 +55,35 @@ class timing_sram_test(openram_test):
data.update(port_data[0]) data.update(port_data[0])
if OPTS.tech_name == "freepdk45": if OPTS.tech_name == "freepdk45":
golden_data = {'slew_lh': [0.2592187], golden_data = {'delay_hl': [0.24671600000000002],
'slew_hl': [0.2592187], 'delay_lh': [0.24671600000000002],
'delay_lh': [0.2465583], 'disabled_read0_power': [0.1749204],
'disabled_write0_power': [0.1924678], 'disabled_read1_power': [0.1873704],
'disabled_read0_power': [0.152483], 'disabled_write0_power': [0.204619],
'write0_power': [0.3409064], 'disabled_write1_power': [0.2262653],
'disabled_read1_power': [0.1737818], 'leakage_power': 0.0021375310000000002,
'read0_power': [0.3096708], 'min_period': 0.977,
'read1_power': [0.3107916], 'read0_power': [0.3856875],
'delay_hl': [0.2465583], 'read1_power': [0.38856060000000003],
'write1_power': [0.26915849999999997], 'slew_hl': [0.2842019],
'leakage_power': 0.002044307, 'slew_lh': [0.2842019],
'min_period': 0.898, 'write0_power': [0.45274410000000004],
'disabled_write1_power': [0.201411]} 'write1_power': [0.38727789999999995]}
elif OPTS.tech_name == "scn4m_subm": elif OPTS.tech_name == "scn4m_subm":
golden_data = {'delay_hl': [1.8435739999999998], golden_data = {'delay_hl': [1.882508],
'delay_lh': [1.8435739999999998], 'delay_lh': [1.882508],
'disabled_read0_power': [5.917947], 'disabled_read0_power': [7.487227],
'disabled_read1_power': [7.154297], 'disabled_read1_power': [8.749013],
'disabled_write0_power': [7.696351], 'disabled_write0_power': [9.268901],
'disabled_write1_power': [7.999409000000001], 'disabled_write1_power': [9.962973],
'leakage_power': 0.004809726, 'leakage_power': 0.0046686359999999994,
'min_period': 6.875, 'min_period': 7.188,
'read0_power': [11.833079999999999], 'read0_power': [16.64011],
'read1_power': [11.99236], 'read1_power': [17.20825],
'slew_hl': [1.8668490000000002], 'slew_hl': [2.039655],
'slew_lh': [1.8668490000000002], 'slew_lh': [2.039655],
'write0_power': [13.287510000000001], 'write0_power': [19.31883],
'write1_power': [10.416369999999999]} 'write1_power': [15.297369999999999]}
else: else:
self.assertTrue(False) # other techs fail self.assertTrue(False) # other techs fail

View File

@ -12,8 +12,7 @@ import sys, os
sys.path.append(os.getenv("OPENRAM_HOME")) sys.path.append(os.getenv("OPENRAM_HOME"))
import globals import globals
from globals import OPTS from globals import OPTS
from sram_factory import factory
import debug
class timing_setup_test(openram_test): class timing_setup_test(openram_test):
@ -29,14 +28,12 @@ class timing_setup_test(openram_test):
import characterizer import characterizer
reload(characterizer) reload(characterizer)
from characterizer import setup_hold from characterizer import setup_hold
import sram
import tech import tech
slews = [tech.spice["rise_time"]*2] slews = [tech.spice["rise_time"]*2]
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
sh = setup_hold(corner) sh = setup_hold(corner)
data = sh.analyze(slews,slews) data = sh.analyze(slews,slews)
#print data
if OPTS.tech_name == "freepdk45": if OPTS.tech_name == "freepdk45":
golden_data = {'hold_times_HL': [-0.01586914], golden_data = {'hold_times_HL': [-0.01586914],
'hold_times_LH': [-0.01586914], 'hold_times_LH': [-0.01586914],
@ -47,6 +44,11 @@ class timing_setup_test(openram_test):
'hold_times_LH': [-0.1293945], 'hold_times_LH': [-0.1293945],
'setup_times_HL': [0.1757812], 'setup_times_HL': [0.1757812],
'setup_times_LH': [0.1879883]} 'setup_times_LH': [0.1879883]}
elif OPTS.tech_name == "sky130":
golden_data = {'hold_times_HL': [-0.05615234],
'hold_times_LH': [-0.03173828],
'setup_times_HL': [0.078125],
'setup_times_LH': [0.1025391]}
else: else:
self.assertTrue(False) # other techs fail self.assertTrue(False) # other techs fail

View File

@ -24,7 +24,7 @@ class psram_1bank_2mux_func_test(openram_test):
OPTS.analytical_delay = False OPTS.analytical_delay = False
OPTS.netlist_only = True OPTS.netlist_only = True
OPTS.trim_netlist = False OPTS.trim_netlist = False
OPTS.bitcell = "pbitcell" OPTS.bitcell = "pbitcell"
OPTS.replica_bitcell="replica_pbitcell" OPTS.replica_bitcell="replica_pbitcell"
OPTS.dummy_bitcell="dummy_pbitcell" OPTS.dummy_bitcell="dummy_pbitcell"
@ -53,10 +53,7 @@ class psram_1bank_2mux_func_test(openram_test):
c.words_per_row, c.words_per_row,
c.num_banks)) c.num_banks))
s = factory.create(module_type="sram", sram_config=c) s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp" f = functional(s.s)
s.sp_write(tempspice)
f = functional(s.s, tempspice)
(fail, error) = f.run() (fail, error) = f.run()
self.assertTrue(fail, error) self.assertTrue(fail, error)

View File

@ -25,7 +25,7 @@ class psram_1bank_4mux_func_test(openram_test):
OPTS.analytical_delay = False OPTS.analytical_delay = False
OPTS.netlist_only = True OPTS.netlist_only = True
OPTS.trim_netlist = False OPTS.trim_netlist = False
OPTS.bitcell = "pbitcell" OPTS.bitcell = "pbitcell"
OPTS.replica_bitcell="replica_pbitcell" OPTS.replica_bitcell="replica_pbitcell"
OPTS.dummy_bitcell="dummy_pbitcell" OPTS.dummy_bitcell="dummy_pbitcell"
@ -54,11 +54,8 @@ class psram_1bank_4mux_func_test(openram_test):
c.words_per_row, c.words_per_row,
c.num_banks)) c.num_banks))
s = factory.create(module_type="sram", sram_config=c) s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp"
s.sp_write(tempspice)
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
f = functional(s.s, tempspice, corner) f = functional(s.s, corner=corner)
(fail, error) = f.run() (fail, error) = f.run()
self.assertTrue(fail, error) self.assertTrue(fail, error)

View File

@ -25,7 +25,7 @@ class psram_1bank_8mux_func_test(openram_test):
OPTS.analytical_delay = False OPTS.analytical_delay = False
OPTS.netlist_only = True OPTS.netlist_only = True
OPTS.trim_netlist = False OPTS.trim_netlist = False
OPTS.bitcell = "pbitcell" OPTS.bitcell = "pbitcell"
OPTS.replica_bitcell="replica_pbitcell" OPTS.replica_bitcell="replica_pbitcell"
OPTS.dummy_bitcell="dummy_pbitcell" OPTS.dummy_bitcell="dummy_pbitcell"
@ -54,10 +54,7 @@ class psram_1bank_8mux_func_test(openram_test):
c.words_per_row, c.words_per_row,
c.num_banks)) c.num_banks))
s = factory.create(module_type="sram", sram_config=c) s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp" f = functional(s.s)
s.sp_write(tempspice)
f = functional(s.s, tempspice)
(fail, error) = f.run() (fail, error) = f.run()
self.assertTrue(fail, error) self.assertTrue(fail, error)

View File

@ -24,7 +24,7 @@ class psram_1bank_nomux_func_test(openram_test):
OPTS.analytical_delay = False OPTS.analytical_delay = False
OPTS.netlist_only = True OPTS.netlist_only = True
OPTS.trim_netlist = False OPTS.trim_netlist = False
OPTS.bitcell = "pbitcell" OPTS.bitcell = "pbitcell"
OPTS.replica_bitcell="replica_pbitcell" OPTS.replica_bitcell="replica_pbitcell"
OPTS.dummy_bitcell="dummy_pbitcell" OPTS.dummy_bitcell="dummy_pbitcell"
@ -53,10 +53,7 @@ class psram_1bank_nomux_func_test(openram_test):
c.words_per_row, c.words_per_row,
c.num_banks)) c.num_banks))
s = factory.create(module_type="sram", sram_config=c) s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp" f = functional(s.s)
s.sp_write(tempspice)
f = functional(s.s, tempspice)
(fail, error) = f.run() (fail, error) = f.run()
self.assertTrue(fail, error) self.assertTrue(fail, error)

View File

@ -25,7 +25,7 @@ class sram_1bank_2mux_func_test(openram_test):
OPTS.analytical_delay = False OPTS.analytical_delay = False
OPTS.netlist_only = True OPTS.netlist_only = True
OPTS.trim_netlist = False OPTS.trim_netlist = False
# This is a hack to reload the characterizer __init__ with the spice version # This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload from importlib import reload
import characterizer import characterizer
@ -43,10 +43,7 @@ class sram_1bank_2mux_func_test(openram_test):
c.words_per_row, c.words_per_row,
c.num_banks)) c.num_banks))
s = factory.create(module_type="sram", sram_config=c) s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp" f = functional(s.s)
s.sp_write(tempspice)
f = functional(s.s, tempspice)
(fail, error) = f.run() (fail, error) = f.run()
self.assertTrue(fail, error) self.assertTrue(fail, error)

View File

@ -25,7 +25,7 @@ class sram_1bank_2mux_func_test(openram_test):
OPTS.analytical_delay = False OPTS.analytical_delay = False
OPTS.netlist_only = True OPTS.netlist_only = True
OPTS.trim_netlist = False OPTS.trim_netlist = False
# This is a hack to reload the characterizer __init__ with the spice version # This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload from importlib import reload
import characterizer import characterizer
@ -44,10 +44,7 @@ class sram_1bank_2mux_func_test(openram_test):
c.words_per_row, c.words_per_row,
c.num_banks)) c.num_banks))
s = factory.create(module_type="sram", sram_config=c) s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp" f = functional(s.s)
s.sp_write(tempspice)
f = functional(s.s, tempspice)
(fail, error) = f.run() (fail, error) = f.run()
self.assertTrue(fail, error) self.assertTrue(fail, error)

View File

@ -45,10 +45,7 @@ class sram_1bank_2mux_sparecols_func_test(openram_test):
c.num_spare_cols, c.num_spare_cols,
c.num_banks)) c.num_banks))
s = factory.create(module_type="sram", sram_config=c) s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp" f = functional(s.s)
s.sp_write(tempspice)
f = functional(s.s, tempspice)
(fail, error) = f.run() (fail, error) = f.run()
self.assertTrue(fail, error) self.assertTrue(fail, error)

View File

@ -25,7 +25,7 @@ class sram_1bank_4mux_func_test(openram_test):
OPTS.analytical_delay = False OPTS.analytical_delay = False
OPTS.netlist_only = True OPTS.netlist_only = True
OPTS.trim_netlist = False OPTS.trim_netlist = False
# This is a hack to reload the characterizer __init__ with the spice version # This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload from importlib import reload
import characterizer import characterizer
@ -43,10 +43,7 @@ class sram_1bank_4mux_func_test(openram_test):
c.words_per_row, c.words_per_row,
c.num_banks)) c.num_banks))
s = factory.create(module_type="sram", sram_config=c) s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp" f = functional(s.s)
s.sp_write(tempspice)
f = functional(s.s, tempspice)
(fail, error) = f.run() (fail, error) = f.run()
self.assertTrue(fail, error) self.assertTrue(fail, error)

View File

@ -25,7 +25,7 @@ class sram_1bank_8mux_func_test(openram_test):
OPTS.analytical_delay = False OPTS.analytical_delay = False
OPTS.netlist_only = True OPTS.netlist_only = True
OPTS.trim_netlist = False OPTS.trim_netlist = False
# This is a hack to reload the characterizer __init__ with the spice version # This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload from importlib import reload
import characterizer import characterizer
@ -46,10 +46,7 @@ class sram_1bank_8mux_func_test(openram_test):
c.words_per_row, c.words_per_row,
c.num_banks)) c.num_banks))
s = factory.create(module_type="sram", sram_config=c) s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp" f = functional(s.s)
s.sp_write(tempspice)
f = functional(s.s, tempspice)
(fail, error) = f.run() (fail, error) = f.run()
self.assertTrue(fail, error) self.assertTrue(fail, error)

View File

@ -24,6 +24,7 @@ class psram_1bank_nomux_func_test(openram_test):
OPTS.analytical_delay = False OPTS.analytical_delay = False
OPTS.netlist_only = True OPTS.netlist_only = True
OPTS.trim_netlist = False OPTS.trim_netlist = False
OPTS.num_rw_ports = 1 OPTS.num_rw_ports = 1
OPTS.num_w_ports = 0 OPTS.num_w_ports = 0
OPTS.num_r_ports = 1 OPTS.num_r_ports = 1
@ -46,10 +47,7 @@ class psram_1bank_nomux_func_test(openram_test):
c.words_per_row, c.words_per_row,
c.num_banks)) c.num_banks))
s = factory.create(module_type="sram", sram_config=c) s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp" f = functional(s.s)
s.sp_write(tempspice)
f = functional(s.s, tempspice)
(fail, error) = f.run() (fail, error) = f.run()
self.assertTrue(fail, error) self.assertTrue(fail, error)

View File

@ -24,6 +24,7 @@ class sram_1bank_nomux_func_test(openram_test):
globals.init_openram(config_file) globals.init_openram(config_file)
OPTS.analytical_delay = False OPTS.analytical_delay = False
OPTS.netlist_only = True OPTS.netlist_only = True
OPTS.trim_netlist = False
# This is a hack to reload the characterizer __init__ with the spice version # This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload from importlib import reload
@ -42,10 +43,7 @@ class sram_1bank_nomux_func_test(openram_test):
c.words_per_row, c.words_per_row,
c.num_banks)) c.num_banks))
s = factory.create(module_type="sram", sram_config=c) s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp" f = functional(s.s)
s.sp_write(tempspice)
f = functional(s.s, tempspice)
(fail, error) = f.run() (fail, error) = f.run()
self.assertTrue(fail, error) self.assertTrue(fail, error)

View File

@ -24,6 +24,7 @@ class sram_1bank_nomux_sparecols_func_test(openram_test):
globals.init_openram(config_file) globals.init_openram(config_file)
OPTS.analytical_delay = False OPTS.analytical_delay = False
OPTS.netlist_only = True OPTS.netlist_only = True
OPTS.trim_netlist = False
# This is a hack to reload the characterizer __init__ with the spice version # This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload from importlib import reload
@ -43,10 +44,7 @@ class sram_1bank_nomux_sparecols_func_test(openram_test):
c.words_per_row, c.words_per_row,
c.num_banks)) c.num_banks))
s = factory.create(module_type="sram", sram_config=c) s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp" f = functional(s.s)
s.sp_write(tempspice)
f = functional(s.s, tempspice)
(fail, error) = f.run() (fail, error) = f.run()
self.assertTrue(fail, error) self.assertTrue(fail, error)

View File

@ -24,6 +24,7 @@ class sram_wmask_1w_1r_func_test(openram_test):
OPTS.analytical_delay = False OPTS.analytical_delay = False
OPTS.netlist_only = True OPTS.netlist_only = True
OPTS.trim_netlist = False OPTS.trim_netlist = False
OPTS.num_rw_ports = 1 OPTS.num_rw_ports = 1
OPTS.num_w_ports = 0 OPTS.num_w_ports = 0
OPTS.num_r_ports = 1 OPTS.num_r_ports = 1
@ -49,10 +50,7 @@ class sram_wmask_1w_1r_func_test(openram_test):
c.write_size, c.write_size,
c.num_banks)) c.num_banks))
s = factory.create(module_type="sram", sram_config=c) s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp" f = functional(s.s)
s.sp_write(tempspice)
f = functional(s.s, tempspice)
(fail, error) = f.run() (fail, error) = f.run()
self.assertTrue(fail, error) self.assertTrue(fail, error)

View File

@ -45,10 +45,7 @@ class sram_wmask_func_test(openram_test):
c.write_size, c.write_size,
c.num_banks)) c.num_banks))
s = factory.create(module_type="sram", sram_config=c) s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp" f = functional(s.s)
s.sp_write(tempspice)
f = functional(s.s, tempspice)
(fail, error) = f.run() (fail, error) = f.run()
self.assertTrue(fail, error) self.assertTrue(fail, error)

View File

@ -21,7 +21,6 @@ class lib_test(openram_test):
globals.init_openram(config_file) globals.init_openram(config_file)
OPTS.analytical_delay = False OPTS.analytical_delay = False
OPTS.netlist_only = True OPTS.netlist_only = True
OPTS.trim_netlist = False
# This is a hack to reload the characterizer __init__ with the spice version # This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload from importlib import reload

View File

@ -45,7 +45,7 @@ class sram_pex_test(openram_test):
tempspice = self.run_pex(s) tempspice = self.run_pex(s)
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
f = functional(s.s, tempspice, corner) f = functional(s.s, spfile=tempspice, corner=corner)
(fail, error) = f.run() (fail, error) = f.run()
self.assertTrue(fail, error) self.assertTrue(fail, error)

View File

@ -49,6 +49,8 @@ class openram_back_end_test(openram_test):
if OPTS.tech_name: if OPTS.tech_name:
options += " -t {}".format(OPTS.tech_name) options += " -t {}".format(OPTS.tech_name)
options += " -j 2"
# Always perform code coverage # Always perform code coverage
if OPTS.coverage == 0: if OPTS.coverage == 0:
debug.warning("Failed to find coverage installation. This can be installed with pip3 install coverage") debug.warning("Failed to find coverage installation. This can be installed with pip3 install coverage")

View File

@ -49,6 +49,8 @@ class openram_front_end_test(openram_test):
if OPTS.tech_name: if OPTS.tech_name:
options += " -t {}".format(OPTS.tech_name) options += " -t {}".format(OPTS.tech_name)
options += " -j 2"
# Always perform code coverage # Always perform code coverage
if OPTS.coverage == 0: if OPTS.coverage == 0:
debug.warning("Failed to find coverage installation. This can be installed with pip3 install coverage") debug.warning("Failed to find coverage installation. This can be installed with pip3 install coverage")

View File

@ -24,7 +24,6 @@ class riscv_func_test(openram_test):
globals.init_openram(config_file) globals.init_openram(config_file)
OPTS.analytical_delay = False OPTS.analytical_delay = False
OPTS.netlist_only = True OPTS.netlist_only = True
OPTS.trim_netlist = False
OPTS.local_array_size = 16 OPTS.local_array_size = 16
OPTS.num_rw_ports = 1 OPTS.num_rw_ports = 1
OPTS.num_w_ports = 0 OPTS.num_w_ports = 0
@ -49,11 +48,8 @@ class riscv_func_test(openram_test):
c.words_per_row, c.words_per_row,
c.num_banks)) c.num_banks))
s = factory.create(module_type="sram", sram_config=c) s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp"
s.sp_write(tempspice)
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
f = functional(s.s, tempspice, corner) f = functional(s.s, corner=corner)
(fail, error) = f.run() (fail, error) = f.run()
self.assertTrue(fail, error) self.assertTrue(fail, error)

View File

@ -12,6 +12,8 @@ module sram_2_16_1_freepdk45(
parameter RAM_DEPTH = 1 << ADDR_WIDTH; parameter RAM_DEPTH = 1 << ADDR_WIDTH;
// FIXME: This delay is arbitrary. // FIXME: This delay is arbitrary.
parameter DELAY = 3 ; parameter DELAY = 3 ;
parameter VERBOSE = 1 ; //Set to 0 to only display warnings
parameter T_HOLD = 1 ; //Delay to hold dout value after posedge. Value is arbitrary
input clk0; // clock input clk0; // clock
input csb0; // active low chip select input csb0; // active low chip select
@ -33,10 +35,10 @@ module sram_2_16_1_freepdk45(
web0_reg = web0; web0_reg = web0;
addr0_reg = addr0; addr0_reg = addr0;
din0_reg = din0; din0_reg = din0;
dout0 = 2'bx; #(T_HOLD) dout0 = 2'bx;
if ( !csb0_reg && web0_reg ) if ( !csb0_reg && web0_reg && VERBOSE )
$display($time," Reading %m addr0=%b dout0=%b",addr0_reg,mem[addr0_reg]); $display($time," Reading %m addr0=%b dout0=%b",addr0_reg,mem[addr0_reg]);
if ( !csb0_reg && !web0_reg ) if ( !csb0_reg && !web0_reg && VERBOSE )
$display($time," Writing %m addr0=%b din0=%b",addr0_reg,din0_reg); $display($time," Writing %m addr0=%b din0=%b",addr0_reg,din0_reg);
end end

View File

@ -12,6 +12,8 @@ module sram_2_16_1_scn4m_subm(
parameter RAM_DEPTH = 1 << ADDR_WIDTH; parameter RAM_DEPTH = 1 << ADDR_WIDTH;
// FIXME: This delay is arbitrary. // FIXME: This delay is arbitrary.
parameter DELAY = 3 ; parameter DELAY = 3 ;
parameter VERBOSE = 1 ; //Set to 0 to only display warnings
parameter T_HOLD = 1 ; //Delay to hold dout value after posedge. Value is arbitrary
input clk0; // clock input clk0; // clock
input csb0; // active low chip select input csb0; // active low chip select
@ -33,10 +35,10 @@ module sram_2_16_1_scn4m_subm(
web0_reg = web0; web0_reg = web0;
addr0_reg = addr0; addr0_reg = addr0;
din0_reg = din0; din0_reg = din0;
dout0 = 2'bx; #(T_HOLD) dout0 = 2'bx;
if ( !csb0_reg && web0_reg ) if ( !csb0_reg && web0_reg && VERBOSE )
$display($time," Reading %m addr0=%b dout0=%b",addr0_reg,mem[addr0_reg]); $display($time," Reading %m addr0=%b dout0=%b",addr0_reg,mem[addr0_reg]);
if ( !csb0_reg && !web0_reg ) if ( !csb0_reg && !web0_reg && VERBOSE )
$display($time," Writing %m addr0=%b din0=%b",addr0_reg,din0_reg); $display($time," Writing %m addr0=%b din0=%b",addr0_reg,din0_reg);
end end

View File

@ -13,7 +13,6 @@ import sys, os
sys.path.append(os.getenv("OPENRAM_HOME")) sys.path.append(os.getenv("OPENRAM_HOME"))
import globals import globals
from subunit import ProtocolTestCase, TestProtocolClient from subunit import ProtocolTestCase, TestProtocolClient
from subunit.test_results import AutoTimingTestResultDecorator
from testtools import ConcurrentTestSuite from testtools import ConcurrentTestSuite
(OPTS, args) = globals.parse_args() (OPTS, args) = globals.parse_args()
@ -71,7 +70,7 @@ def fork_tests(num_threads):
stream = os.fdopen(c2pwrite, 'wb', 0) stream = os.fdopen(c2pwrite, 'wb', 0)
os.close(c2pread) os.close(c2pread)
sys.stdin.close() sys.stdin.close()
test_suite_result = AutoTimingTestResultDecorator(TestProtocolClient(stream)) test_suite_result = TestProtocolClient(stream)
test_suite.run(test_suite_result) test_suite.run(test_suite_result)
except EBADF: except EBADF:
try: try:

View File

@ -12,11 +12,36 @@ from globals import OPTS
import debug import debug
import pdb import pdb
import traceback import traceback
import time
class openram_test(unittest.TestCase): class openram_test(unittest.TestCase):
""" Base unit test that we have some shared classes in. """ """ Base unit test that we have some shared classes in. """
def setUp(self):
self.start_time = time.time()
def tearDown(self):
duration = time.time() - self.start_time
print('%s: %.3fs' % (self.id(), duration))
def fail(self, msg):
import inspect
s = inspect.stack()
base_filename = os.path.splitext(os.path.basename(s[2].filename))[0]
try:
OPENRAM_HOME = os.path.abspath(os.environ.get("OPENRAM_HOME"))
except:
debug.error("$OPENRAM_HOME is not properly defined.", 1)
import shutil
zip_file = "{0}/../{1}_{2}".format(OPENRAM_HOME, base_filename, os.getpid())
debug.info(0, "Archiving failed temp files {0} to {1}".format(OPTS.openram_temp, zip_file))
shutil.make_archive(zip_file, 'zip', OPTS.openram_temp)
super().fail(msg)
def local_drc_check(self, w): def local_drc_check(self, w):
self.reset() self.reset()
@ -28,10 +53,9 @@ class openram_test(unittest.TestCase):
result=verify.run_drc(w.name, tempgds, None) result=verify.run_drc(w.name, tempgds, None)
if result != 0: if result != 0:
self.fail("DRC failed: {}".format(w.name)) self.fail("DRC failed: {}".format(w.name))
elif not OPTS.keep_temp:
if not OPTS.keep_temp:
self.cleanup() self.cleanup()
def local_check(self, a, final_verification=False): def local_check(self, a, final_verification=False):
self.reset() self.reset()
@ -39,7 +63,7 @@ class openram_test(unittest.TestCase):
tempspice = "{}.sp".format(a.name) tempspice = "{}.sp".format(a.name)
tempgds = "{}.gds".format(a.name) tempgds = "{}.gds".format(a.name)
a.lvs_write("{0}{1}".format(OPTS.openram_temp, tempspice)) a.sp_write("{0}{1}".format(OPTS.openram_temp, tempspice), lvs=True)
# cannot write gds in netlist_only mode # cannot write gds in netlist_only mode
if not OPTS.netlist_only: if not OPTS.netlist_only:
a.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds)) a.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds))
@ -74,10 +98,10 @@ class openram_test(unittest.TestCase):
# shutil.make_archive(zip_file, 'zip', OPTS.openram_temp) # shutil.make_archive(zip_file, 'zip', OPTS.openram_temp)
self.fail("LVS mismatch: {}".format(a.name)) self.fail("LVS mismatch: {}".format(a.name))
if lvs_result == 0 and drc_result == 0 and not OPTS.keep_temp:
self.cleanup()
# For debug... # For debug...
# import pdb; pdb.set_trace() # import pdb; pdb.set_trace()
if not OPTS.keep_temp:
self.cleanup()
def run_pex(self, a, output=None): def run_pex(self, a, output=None):
tempspice = "{}.sp".format(a.name) tempspice = "{}.sp".format(a.name)
@ -104,6 +128,7 @@ class openram_test(unittest.TestCase):
def cleanup(self): def cleanup(self):
""" Reset the duplicate checker and cleanup files. """ """ Reset the duplicate checker and cleanup files. """
files = glob.glob(OPTS.openram_temp + '*') files = glob.glob(OPTS.openram_temp + '*')
for f in files: for f in files:
# Only remove the files # Only remove the files

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,236 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="605.40302"
height="165.26472"
viewBox="0 0 605.40301 165.26473"
id="svg2"
version="1.1"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
sodipodi:docname="OpenRAM_logo_yellow_transparent.svg"
inkscape:export-filename="/home/mrg/openram/images/OpenRAM_logo_yellow_transparent.png"
inkscape:export-xdpi="150"
inkscape:export-ydpi="150">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2634"
inkscape:window-height="1333"
id="namedview3551"
showgrid="false"
inkscape:zoom="2.8592481"
inkscape:cx="232.75305"
inkscape:cy="82.632362"
inkscape:window-x="208"
inkscape:window-y="96"
inkscape:window-maximized="0"
inkscape:current-layer="svg2"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<defs
id="defs4" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<text
id="text3336"
y="113.18625"
x="173.17645"
style="font-style:normal;font-weight:normal;font-size:22.5px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#003c6c;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
id="tspan4140"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:80px;font-family:Futura;-inkscape-font-specification:'Futura Bold';fill:#003c6c;fill-opacity:1"
y="113.18625"
x="173.17645">OpenRAM</tspan></text>
<rect
y="39.336884"
x="36.581963"
height="87.463478"
width="89.973282"
id="rect4144"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:8.3992691;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path8080"
d="m 53.960768,13.421563 v 21.96078"
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 81.568617,13.421563 v 21.96078"
id="path8112"
inkscape:connector-curvature="0" />
<path
id="path8118"
d="m 109.17646,13.421563 v 21.96078"
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 53.960768,151.84317 V 129.88239"
id="path8137"
inkscape:connector-curvature="0" />
<path
id="path8143"
d="M 81.568617,151.84317 V 129.88239"
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 109.17646,151.84317 V 129.88239"
id="path8149"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 151.21568,56.715693 H 129.2549"
id="path8157"
inkscape:connector-curvature="0" />
<path
id="path8163"
d="M 151.21568,84.323543 H 129.2549"
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 151.21568,111.93138 H 129.2549"
id="path8169"
inkscape:connector-curvature="0" />
<path
id="path8177"
d="m 13.421548,56.715693 h 21.96078"
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 13.421548,84.323543 h 21.96078"
id="path8183"
inkscape:connector-curvature="0" />
<path
id="path8189"
d="m 13.421548,111.93138 h 21.96078"
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
<ellipse
ry="5.9607844"
rx="5.647059"
cy="7.4607844"
cx="53.96077"
id="path8078"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="ellipse8110"
cx="81.568619"
cy="7.4607844"
rx="5.647059"
ry="5.9607844" />
<ellipse
ry="5.9607844"
rx="5.647059"
cy="7.4607844"
cx="109.17645"
id="ellipse8116"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
transform="scale(1,-1)"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="ellipse8135"
cx="53.96077"
cy="-157.80394"
rx="5.647059"
ry="5.9607844" />
<ellipse
transform="scale(1,-1)"
ry="5.9607844"
rx="5.647059"
cy="-157.80394"
cx="81.568619"
id="ellipse8141"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
transform="scale(1,-1)"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="ellipse8147"
cx="109.17645"
cy="-157.80394"
rx="5.647059"
ry="5.9607844" />
<ellipse
transform="rotate(90)"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="ellipse8155"
cx="56.715668"
cy="-157.17644"
rx="5.647059"
ry="5.9607844" />
<ellipse
transform="rotate(90)"
ry="5.9607844"
rx="5.647059"
cy="-157.17644"
cx="84.323517"
id="ellipse8161"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
transform="rotate(90)"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="ellipse8167"
cx="111.93137"
cy="-157.17644"
rx="5.647059"
ry="5.9607844" />
<ellipse
transform="matrix(0,1,1,0,0,0)"
ry="5.9607844"
rx="5.647059"
cy="7.4607844"
cx="56.715668"
id="ellipse8175"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
transform="matrix(0,1,1,0,0,0)"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="ellipse8181"
cx="84.323517"
cy="7.4607844"
rx="5.647059"
ry="5.9607844" />
<ellipse
transform="matrix(0,1,1,0,0,0)"
ry="5.9607844"
rx="5.647059"
cy="7.4607844"
cx="111.93137"
id="ellipse8187"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="fill:#fdc700;fill-opacity:1;stroke:none;stroke-width:4.87900019;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect3553"
width="88.83606"
height="86.559578"
x="37.165073"
y="39.245544"
ry="4.3552427" />
</svg>

After

Width:  |  Height:  |  Size: 9.7 KiB