mirror of https://github.com/VLSIDA/OpenRAM.git
Merge branch 'dev' into laptop_checkpoint
This commit is contained in:
commit
6ea4bdc5e5
|
|
@ -1 +1,2 @@
|
|||
*.sp linguist-vendored
|
||||
*.sp linguist-language=Spice
|
||||
*.tf linquist-language=Tech File
|
||||
|
|
|
|||
|
|
@ -9,17 +9,17 @@ jobs:
|
|||
- name: SCMOS test
|
||||
run: |
|
||||
. /home/github-runner/setup-paths.sh
|
||||
export OPENRAM_HOME="`pwd`/compiler"
|
||||
export OPENRAM_TECH="`pwd`/technology:/software/PDKs/skywater-tech"
|
||||
export OPENRAM_HOME="${{ github.workspace }}/compiler"
|
||||
export OPENRAM_TECH="${{ github.workspace }}/technology:/software/PDKs/skywater-tech"
|
||||
export OPENRAM_TMP="${{ github.workspace }}/scn4me_subm_temp"
|
||||
#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
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: scn4me_subm Archives
|
||||
path: ${{ github.workspace }}/scn4me_subm_temp/*/*
|
||||
path: ${{ github.workspace }}/*.zip
|
||||
freepdk45:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
|
|
@ -28,17 +28,17 @@ jobs:
|
|||
- name: FreePDK45 test
|
||||
run: |
|
||||
. /home/github-runner/setup-paths.sh
|
||||
export OPENRAM_HOME="`pwd`/compiler"
|
||||
export OPENRAM_TECH="`pwd`/technology:/software/PDKs/skywater-tech"
|
||||
export OPENRAM_HOME="${{ github.workspace }}/compiler"
|
||||
export OPENRAM_TECH="${{ github.workspace }}/technology:/software/PDKs/skywater-tech"
|
||||
export OPENRAM_TMP="${{ github.workspace }}/freepdk45_temp"
|
||||
#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
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: FreePDK45 Archives
|
||||
path: ${{ github.workspace }}/freepdk45_temp/*/*
|
||||
path: ${{ github.workspace }}/*.zip
|
||||
# coverage_stats:
|
||||
# if: ${{ always() }}
|
||||
# needs: [scn4me_subm, freepdk45]
|
||||
|
|
|
|||
12
README.md
12
README.md
|
|
@ -1,12 +1,9 @@
|
|||

|
||||
# OpenRAM
|
||||
|
||||
[](https://www.python.org/)
|
||||
[](./LICENSE)
|
||||
|
||||
Master:
|
||||
[](https://github.com/VLSIDA/OpenRAM/archive/master.zip)
|
||||
|
||||
Dev:
|
||||
[](https://github.com/VLSIDA/OpenRAM/archive/stable.zip)
|
||||
[](https://github.com/VLSIDA/OpenRAM/archive/dev.zip)
|
||||
|
||||
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:
|
||||
+ [Ngspice] 26 (or later) or HSpice I-2013.12-1 (or later) or CustomSim 2017 (or later)
|
||||
+ Python 3.5 or higher
|
||||
+ Python numpy (pip3 install numpy to install)
|
||||
+ Python scipy (pip3 install scipy to install)
|
||||
+ Various Python packages (pip install -r requirements.txt)
|
||||
|
||||
If you want to perform DRC and LVS, you will need either:
|
||||
+ Calibre (for [FreePDK45])
|
||||
|
|
@ -145,7 +141,7 @@ The default for openram.py is specified in the configuration file.
|
|||
|
||||
# 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 technology directory for each technology with the base cells
|
||||
|
||||
|
|
|
|||
|
|
@ -123,6 +123,14 @@ class _wordline_driver:
|
|||
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():
|
||||
"""
|
||||
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._local_bitcell_array = _bitcell_array(wordline_layer="m2")
|
||||
|
||||
self._global_bitcell_array = _bitcell_array(wordline_layer="m3")
|
||||
|
||||
@property
|
||||
def bank(self):
|
||||
return self._bank
|
||||
|
|
@ -191,3 +203,11 @@ class layer_properties():
|
|||
def wordline_driver(self):
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
|||
elif (OPTS.inline_lvsdrc or force_check or final_verification):
|
||||
|
||||
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)
|
||||
self.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds))
|
||||
# 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
|
||||
elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
|
||||
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)
|
||||
self.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds))
|
||||
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
|
||||
elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
|
||||
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)
|
||||
self.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds))
|
||||
num_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification=final_verification)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from tech import layer_indices
|
|||
from tech import layer_stacks
|
||||
from tech import preferred_directions
|
||||
import os
|
||||
import sys
|
||||
from globals import OPTS
|
||||
from vector import vector
|
||||
from pin_layout import pin_layout
|
||||
|
|
@ -111,52 +112,48 @@ class layout():
|
|||
Finds the lowest set of 2d cartesian coordinates within
|
||||
this layout
|
||||
"""
|
||||
lowestx = lowesty = sys.maxsize
|
||||
|
||||
if len(self.objs) > 0:
|
||||
lowestx1 = min(obj.lx() for obj in self.objs if obj.name != "label")
|
||||
lowesty1 = min(obj.by() for obj in self.objs if obj.name != "label")
|
||||
else:
|
||||
lowestx1 = lowesty1 = None
|
||||
lowestx = min(min(obj.lx() for obj in self.objs if obj.name != "label"), lowestx)
|
||||
lowesty = min(min(obj.by() for obj in self.objs if obj.name != "label"), lowesty)
|
||||
|
||||
if len(self.insts) > 0:
|
||||
lowestx2 = min(inst.lx() for inst in self.insts)
|
||||
lowesty2 = min(inst.by() for inst in self.insts)
|
||||
else:
|
||||
lowestx2 = lowesty2 = None
|
||||
lowestx = min(min(inst.lx() for inst in self.insts), lowestx)
|
||||
lowesty = min(min(inst.by() for inst in self.insts), lowesty)
|
||||
|
||||
if lowestx1 == None and lowestx2 == None:
|
||||
return None
|
||||
elif lowestx1 == None:
|
||||
return vector(lowestx2, lowesty2)
|
||||
elif lowestx2 == None:
|
||||
return vector(lowestx1, lowesty1)
|
||||
else:
|
||||
return vector(min(lowestx1, lowestx2), min(lowesty1, lowesty2))
|
||||
if len(self.pin_map) > 0:
|
||||
for pin_set in self.pin_map.values():
|
||||
if len(pin_set) == 0:
|
||||
continue
|
||||
lowestx = min(min(pin.lx() for pin in pin_set), lowestx)
|
||||
lowesty = min(min(pin.by() for pin in pin_set), lowesty)
|
||||
|
||||
return vector(lowestx, lowesty)
|
||||
|
||||
def find_highest_coords(self):
|
||||
"""
|
||||
Finds the highest set of 2d cartesian coordinates within
|
||||
this layout
|
||||
"""
|
||||
highestx = highesty = -sys.maxsize - 1
|
||||
|
||||
if len(self.objs) > 0:
|
||||
highestx1 = max(obj.rx() for obj in self.objs if obj.name != "label")
|
||||
highesty1 = max(obj.uy() for obj in self.objs if obj.name != "label")
|
||||
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
|
||||
highestx = max(max(obj.rx() for obj in self.objs if obj.name != "label"), highestx)
|
||||
highesty = max(max(obj.uy() for obj in self.objs if obj.name != "label"), highesty)
|
||||
|
||||
if highestx1 == None and highestx2 == None:
|
||||
return None
|
||||
elif highestx1 == None:
|
||||
return vector(highestx2, highesty2)
|
||||
elif highestx2 == None:
|
||||
return vector(highestx1, highesty1)
|
||||
else:
|
||||
return vector(max(highestx1, highestx2),
|
||||
max(highesty1, highesty2))
|
||||
if len(self.insts) > 0:
|
||||
highestx = max(max(inst.rx() for inst in self.insts), highestx)
|
||||
highesty = max(max(inst.uy() for inst in self.insts), highesty)
|
||||
|
||||
if len(self.pin_map) > 0:
|
||||
for pin_set in self.pin_map.values():
|
||||
if len(pin_set) == 0:
|
||||
continue
|
||||
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):
|
||||
"""
|
||||
|
|
@ -677,7 +674,8 @@ class layout():
|
|||
directions=None,
|
||||
size=[1, 1],
|
||||
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.
|
||||
"""
|
||||
|
|
@ -711,7 +709,7 @@ class layout():
|
|||
implant_type=implant_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,
|
||||
offset,
|
||||
via.mod.first_layer_width,
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ class spice():
|
|||
self.conns = []
|
||||
# If this is set, it will out output subckt or isntances of this (for row/col caps etc.)
|
||||
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
|
||||
try:
|
||||
self.commments
|
||||
|
|
@ -312,10 +314,11 @@ class spice():
|
|||
return True
|
||||
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;
|
||||
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:
|
||||
|
|
@ -328,7 +331,7 @@ class spice():
|
|||
if self.contains(i, usedMODS):
|
||||
continue
|
||||
usedMODS.append(i)
|
||||
i.sp_write_file(sp, usedMODS, lvs_netlist)
|
||||
i.sp_write_file(sp, usedMODS, lvs, trim)
|
||||
|
||||
if len(self.insts) == 0:
|
||||
return
|
||||
|
|
@ -371,10 +374,16 @@ class spice():
|
|||
# these are wires and paths
|
||||
if self.conns[i] == []:
|
||||
continue
|
||||
|
||||
# Instance with no devices in it needs no subckt/instance
|
||||
if self.insts[i].mod.no_instances:
|
||||
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,
|
||||
" ".join(self.conns[i])))
|
||||
sp.write("\n")
|
||||
|
|
@ -394,30 +403,20 @@ class spice():
|
|||
# Including the file path makes the unit test fail for other users.
|
||||
# if os.path.isfile(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))
|
||||
else:
|
||||
sp.write("\n".join(self.spice))
|
||||
|
||||
sp.write("\n")
|
||||
|
||||
def sp_write(self, spname):
|
||||
def sp_write(self, spname, lvs=False, trim=False):
|
||||
"""Writes the spice 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)
|
||||
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)
|
||||
self.sp_write_file(spfile, usedMODS, lvs=lvs, trim=trim)
|
||||
del usedMODS
|
||||
spfile.close()
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,11 @@
|
|||
#
|
||||
import debug
|
||||
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:
|
||||
|
|
@ -23,11 +28,105 @@ class lef:
|
|||
# Round to ensure float values are divisible by 0.0025 (the manufacturing grid)
|
||||
self.round_grid = 4
|
||||
|
||||
def lef_write(self, lef_name):
|
||||
"""Write the entire lef of the object to the file."""
|
||||
debug.info(3, "Writing to {0}".format(lef_name))
|
||||
def magic_lef_write(self, lef_name):
|
||||
""" Use a magic script to perform LEF creation. """
|
||||
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_write_header()
|
||||
|
|
@ -89,24 +188,29 @@ class lef:
|
|||
self.indent = self.indent[:-3]
|
||||
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 """
|
||||
self.lef.write("{0}OBS\n".format(self.indent))
|
||||
for layer in self.lef_layers:
|
||||
self.lef.write("{0}LAYER {1} ;\n".format(self.indent, layer_names[layer]))
|
||||
self.indent += " "
|
||||
blockages = self.get_blockages(layer, True)
|
||||
for b in blockages:
|
||||
self.lef_write_shape(b)
|
||||
if abstracted:
|
||||
blockages = self.blockages[layer]
|
||||
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.lef.write("{0}END\n".format(self.indent))
|
||||
|
||||
def lef_write_shape(self, rect):
|
||||
if len(rect) == 2:
|
||||
def lef_write_shape(self, obj):
|
||||
if len(obj) == 2:
|
||||
""" Write a LEF rectangle """
|
||||
self.lef.write("{0}RECT ".format(self.indent))
|
||||
for item in rect:
|
||||
# print(rect)
|
||||
for item in obj:
|
||||
# print(obj)
|
||||
self.lef.write(" {0} {1}".format(round(item[0],
|
||||
self.round_grid),
|
||||
round(item[1],
|
||||
|
|
@ -115,12 +219,10 @@ class lef:
|
|||
else:
|
||||
""" Write a LEF polygon """
|
||||
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.round_grid),
|
||||
round(item[1],
|
||||
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")
|
||||
|
||||
|
|
|
|||
|
|
@ -139,13 +139,13 @@ class pin_layout:
|
|||
min_area = drc("{}_minarea".format(self.layer))
|
||||
pass
|
||||
|
||||
def inflate(self, spacing=None):
|
||||
def inflate(self, spacing=None, multiple=0.5):
|
||||
"""
|
||||
Inflate the rectangle by the spacing (or other rule)
|
||||
and return the new rectangle.
|
||||
"""
|
||||
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
|
||||
spacing = vector(spacing, spacing)
|
||||
|
|
@ -154,15 +154,23 @@ class pin_layout:
|
|||
|
||||
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):
|
||||
""" Check if a shape overlaps with a rectangle """
|
||||
(ll, ur) = self.rect
|
||||
(oll, our) = other.rect
|
||||
|
||||
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)
|
||||
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)]
|
||||
|
||||
|
|
@ -369,11 +377,23 @@ class pin_layout:
|
|||
debug.info(4, "writing pin (" + str(self.layer) + "):"
|
||||
+ str(self.width()) + "x"
|
||||
+ 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:
|
||||
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:
|
||||
pin_purpose = purpose
|
||||
pass
|
||||
|
||||
try:
|
||||
from tech import label_purpose
|
||||
except ImportError:
|
||||
|
|
@ -385,9 +405,9 @@ class pin_layout:
|
|||
width=self.width(),
|
||||
height=self.height(),
|
||||
center=False)
|
||||
# Draw a second pin shape too
|
||||
if pin_purpose != purpose:
|
||||
newLayout.addBox(layerNumber=layer_num,
|
||||
# Draw a second pin shape too if it is different
|
||||
if not self.same_lpp((pin_layer_num, pin_purpose), (layer_num, purpose)):
|
||||
newLayout.addBox(layerNumber=pin_layer_num,
|
||||
purposeNumber=pin_purpose,
|
||||
offsetInMicrons=self.ll(),
|
||||
width=self.width(),
|
||||
|
|
@ -566,6 +586,30 @@ class pin_layout:
|
|||
|
||||
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):
|
||||
"""
|
||||
Check if the layers and purposes are the same.
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ class verilog:
|
|||
self.vf.write(" parameter RAM_DEPTH = 1 << ADDR_WIDTH;\n")
|
||||
self.vf.write(" // FIXME: This delay is arbitrary.\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")
|
||||
|
||||
for port in self.all_ports:
|
||||
|
|
@ -128,21 +130,21 @@ class verilog:
|
|||
if port in self.write_ports:
|
||||
self.vf.write(" din{0}_reg = din{0};\n".format(port))
|
||||
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:
|
||||
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))
|
||||
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))
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ def parse_spice_list(filename, key):
|
|||
f = open(full_filename, "r")
|
||||
except IOError:
|
||||
debug.error("Unable to open spice output file: {0}".format(full_filename),1)
|
||||
debug.archive()
|
||||
|
||||
contents = f.read()
|
||||
f.close()
|
||||
# val = re.search(r"{0}\s*=\s*(-?\d+.?\d*\S*)\s+.*".format(key), contents)
|
||||
|
|
|
|||
|
|
@ -1100,14 +1100,8 @@ class delay(simulation):
|
|||
|
||||
# Set up to trim the netlist here if that is enabled
|
||||
if OPTS.trim_netlist:
|
||||
self.trim_sp_file = "{}reduced.sp".format(OPTS.openram_temp)
|
||||
self.trimsp=trim_spice(self.sp_file, self.trim_sp_file)
|
||||
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)
|
||||
self.trim_sp_file = "{}trimmed.sp".format(OPTS.openram_temp)
|
||||
self.sram.sp_write(self.trim_sp_file, lvs=False, trim=True)
|
||||
else:
|
||||
# The non-reduced netlist file when it is disabled
|
||||
self.trim_sp_file = "{}sram.sp".format(OPTS.openram_temp)
|
||||
|
|
|
|||
|
|
@ -78,8 +78,6 @@ class elmore(simulation):
|
|||
port_data[port][mname].append(total_delay.delay / 1e3)
|
||||
elif "slew" in mname and port in self.read_ports:
|
||||
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
|
||||
# memory. FIXME: margin is quite large, should be looked into.
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import collections
|
|||
import debug
|
||||
import random
|
||||
import math
|
||||
from numpy import binary_repr
|
||||
from .stimuli import *
|
||||
from .charutils import *
|
||||
from globals import OPTS
|
||||
|
|
@ -21,13 +22,17 @@ class functional(simulation):
|
|||
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)
|
||||
|
||||
# Seed the characterizer with a constant seed for unit tests
|
||||
if OPTS.is_unit_test:
|
||||
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:
|
||||
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:
|
||||
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.set_corner(corner)
|
||||
self.set_spice_constants()
|
||||
|
|
@ -66,6 +87,7 @@ class functional(simulation):
|
|||
self.num_cycles = cycles
|
||||
# This is to have ordered keys for random selection
|
||||
self.stored_words = collections.OrderedDict()
|
||||
self.stored_spares = collections.OrderedDict()
|
||||
self.read_check = []
|
||||
self.read_results = []
|
||||
|
||||
|
|
@ -121,10 +143,12 @@ class functional(simulation):
|
|||
# 1. Write all the write ports first to seed a bunch of locations.
|
||||
for port in self.write_ports:
|
||||
addr = self.gen_addr()
|
||||
word = self.gen_data()
|
||||
comment = self.gen_cycle_comment("write", word, addr, "1" * self.num_wmasks, port, self.t_current)
|
||||
self.add_write_one_port(comment, addr, word, "1" * self.num_wmasks, port)
|
||||
(word, spare) = self.gen_data()
|
||||
combined_word = "{}+{}".format(word, spare)
|
||||
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_spares[addr[:self.addr_spare_index]] = spare
|
||||
|
||||
# All other read-only ports are noops.
|
||||
for port in self.read_ports:
|
||||
|
|
@ -142,7 +166,9 @@ class functional(simulation):
|
|||
if port in self.write_ports:
|
||||
self.add_noop_one_port(port)
|
||||
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_check(word, port)
|
||||
self.cycle_times.append(self.t_current)
|
||||
|
|
@ -170,27 +196,33 @@ class functional(simulation):
|
|||
if addr in w_addrs:
|
||||
self.add_noop_one_port(port)
|
||||
else:
|
||||
word = self.gen_data()
|
||||
comment = self.gen_cycle_comment("write", word, addr, "1" * self.num_wmasks, port, self.t_current)
|
||||
self.add_write_one_port(comment, addr, word, "1" * self.num_wmasks, port)
|
||||
(word, spare) = self.gen_data()
|
||||
combined_word = "{}+{}".format(word, spare)
|
||||
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_spares[addr[:self.addr_spare_index]] = spare
|
||||
w_addrs.append(addr)
|
||||
elif op == "partial_write":
|
||||
# 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
|
||||
if addr in w_addrs:
|
||||
self.add_noop_one_port(port)
|
||||
else:
|
||||
word = self.gen_data()
|
||||
(word, spare) = self.gen_data()
|
||||
wmask = self.gen_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)
|
||||
self.add_write_one_port(comment, addr, word, wmask, port)
|
||||
combined_word = "{}+{}".format(word, spare)
|
||||
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_spares[addr[:self.addr_spare_index]] = spare
|
||||
w_addrs.append(addr)
|
||||
else:
|
||||
(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
|
||||
# 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
|
||||
|
|
@ -198,9 +230,9 @@ class functional(simulation):
|
|||
if addr in w_addrs:
|
||||
self.add_noop_one_port(port)
|
||||
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_check(word, port)
|
||||
self.add_read_check(word + spare, port)
|
||||
|
||||
self.cycle_times.append(self.t_current)
|
||||
self.t_current += self.period
|
||||
|
|
@ -227,18 +259,18 @@ class functional(simulation):
|
|||
def add_read_check(self, word, port):
|
||||
""" Add to the check array to ensure a read works. """
|
||||
try:
|
||||
self.check
|
||||
self.check_count
|
||||
except:
|
||||
self.check = 0
|
||||
self.read_check.append([word, "{0}{1}".format(self.dout_name, port), self.t_current + self.period, self.check])
|
||||
self.check += 1
|
||||
self.check_count = 0
|
||||
self.read_check.append([word, "{0}{1}".format(self.dout_name, port), self.t_current + self.period, self.check_count])
|
||||
self.check_count += 1
|
||||
|
||||
def read_stim_results(self):
|
||||
# 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 = ""
|
||||
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:
|
||||
value = float(value)
|
||||
if value > self.v_high:
|
||||
|
|
@ -260,13 +292,13 @@ class functional(simulation):
|
|||
|
||||
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")
|
||||
|
||||
def check_stim_results(self):
|
||||
for i in range(len(self.read_check)):
|
||||
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],
|
||||
self.read_results[i][0],
|
||||
self.read_check[i][0],
|
||||
|
|
@ -300,22 +332,22 @@ class functional(simulation):
|
|||
|
||||
def gen_data(self):
|
||||
""" Generates a random word to write. """
|
||||
if not self.num_spare_cols:
|
||||
random_value = random.randint(0, (2 ** self.word_size) - 1)
|
||||
random_value = random.randint(0, self.max_data)
|
||||
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:
|
||||
random_value1 = random.randint(0, (2 ** self.word_size) - 1)
|
||||
random_value2 = random.randint(0, (2 ** self.num_spare_cols) - 1)
|
||||
random_value = random_value1 + random_value2
|
||||
data_bits = self.convert_to_bin(random_value, False)
|
||||
return data_bits
|
||||
spare_bits = ""
|
||||
return data_bits, spare_bits
|
||||
|
||||
def gen_addr(self):
|
||||
""" Generates a random address value to write to. """
|
||||
if self.num_spare_rows==0:
|
||||
random_value = random.randint(0, (2 ** self.addr_size) - 1)
|
||||
if self.valid_addresses:
|
||||
random_value = random.sample(self.valid_addresses, 1)[0]
|
||||
else:
|
||||
random_value = random.randint(0, ((2 ** (self.addr_size - 1) - 1)) + (self.num_spare_rows * self.words_per_row))
|
||||
addr_bits = self.convert_to_bin(random_value, True)
|
||||
random_value = random.randint(0, self.max_address)
|
||||
addr_bits = binary_repr(random_value, self.addr_size)
|
||||
return addr_bits
|
||||
|
||||
def get_data(self):
|
||||
|
|
@ -323,20 +355,8 @@ class functional(simulation):
|
|||
# Used for write masks since they should be writing to previously written addresses
|
||||
addr = random.choice(list(self.stored_words.keys()))
|
||||
word = self.stored_words[addr]
|
||||
return (addr, word)
|
||||
|
||||
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
|
||||
spare = self.stored_spares[addr[:self.addr_spare_index]]
|
||||
return (addr, word, spare)
|
||||
|
||||
def write_functional_stimulus(self):
|
||||
""" Writes SPICE stimulus. """
|
||||
|
|
@ -432,12 +452,21 @@ class functional(simulation):
|
|||
# Generate dout value measurements
|
||||
self.sf.write("\n * Generation of dout measurements\n")
|
||||
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
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
self.stim.gen_meas_value(meas_name="V{0}_{1}ck{2}".format(dout_port, bit, check),
|
||||
dout="{0}_{1}".format(dout_port, bit),
|
||||
t_intital=t_intital,
|
||||
num_bits = self.word_size + self.num_spare_cols
|
||||
for bit in range(num_bits):
|
||||
measure_name = "V{0}_{1}ck{2}".format(dout_port, bit, check)
|
||||
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)
|
||||
|
||||
self.stim.write_control(self.cycle_times[-1] + self.period)
|
||||
|
|
|
|||
|
|
@ -5,9 +5,8 @@
|
|||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
import os,sys,re
|
||||
import os
|
||||
import debug
|
||||
import math
|
||||
import datetime
|
||||
from .setup_hold import *
|
||||
from .delay import *
|
||||
|
|
@ -16,6 +15,7 @@ import tech
|
|||
import numpy as np
|
||||
from globals import OPTS
|
||||
|
||||
|
||||
class lib:
|
||||
""" lib file generation."""
|
||||
|
||||
|
|
@ -601,7 +601,6 @@ class lib:
|
|||
from .elmore import elmore as model
|
||||
else:
|
||||
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)
|
||||
char_results = m.get_lib_values(self.slews,self.loads)
|
||||
|
|
@ -834,4 +833,4 @@ class lib:
|
|||
#FIXME: should be read_fall_power
|
||||
datasheet.write("{0},{1},".format('write_fall_power_{}'.format(port), read0_power))
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,13 @@ class setup_hold():
|
|||
|
||||
"""
|
||||
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":
|
||||
init_value = incorrect_value
|
||||
start_value = correct_value
|
||||
|
|
|
|||
|
|
@ -27,7 +27,10 @@ class simulation():
|
|||
self.num_spare_cols = 0
|
||||
else:
|
||||
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.readwrite_ports = self.sram.readwrite_ports
|
||||
|
|
|
|||
|
|
@ -169,22 +169,14 @@ class stimuli():
|
|||
def gen_constant(self, sig_name, v_val):
|
||||
""" 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))
|
||||
|
||||
def get_inverse_voltage(self, value):
|
||||
if value > 0.5 * self.voltage:
|
||||
|
||||
def get_voltage(self, value):
|
||||
if value == "0" or value == 0:
|
||||
return 0
|
||||
elif value <= 0.5 * self.voltage:
|
||||
elif value == "1" or value == 1:
|
||||
return self.voltage
|
||||
else:
|
||||
debug.error("Invalid value to get an inverse 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))
|
||||
debug.error("Invalid value to get a voltage 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):
|
||||
""" Creates the .meas statement for the measurement of delay """
|
||||
|
|
@ -228,8 +220,8 @@ class stimuli():
|
|||
t_initial,
|
||||
t_final))
|
||||
|
||||
def gen_meas_value(self, 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_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_initial, t_final)
|
||||
self.sf.write(measure_string)
|
||||
|
||||
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
|
||||
# Do not remove this or it may not converge due to some "pa_00" nodes
|
||||
# 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":
|
||||
self.sf.write("simulator lang=spectre\n")
|
||||
if OPTS.use_pex:
|
||||
|
|
@ -310,6 +302,9 @@ class stimuli():
|
|||
for item in list(includes):
|
||||
self.sf.write(".include \"{0}\"\n".format(item))
|
||||
|
||||
def add_comment(self, msg):
|
||||
self.sf.write(msg + "\n")
|
||||
|
||||
def write_supply(self):
|
||||
""" Writes supply voltage statements """
|
||||
gnd_node_name = "0"
|
||||
|
|
@ -360,6 +355,8 @@ class stimuli():
|
|||
# -r {2}timing.raw
|
||||
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 ngbehavior=hsa\n")
|
||||
ng_cfg.write("set ng_nomodcheck\n")
|
||||
ng_cfg.close()
|
||||
|
||||
cmd = "{0} -b -o {2}timing.lis {1}".format(OPTS.spice_exe,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import debug
|
|||
from math import log,ceil
|
||||
import re
|
||||
|
||||
|
||||
class trim_spice():
|
||||
"""
|
||||
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)):
|
||||
self.spice[i] = self.spice[i].rstrip(" \n")
|
||||
|
||||
|
||||
self.sp_buffer = self.spice
|
||||
|
||||
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.bank_addr_size = self.col_addr_size + self.row_addr_size
|
||||
self.addr_size = self.bank_addr_size + int(log(self.num_banks, 2))
|
||||
|
||||
|
||||
|
||||
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
|
||||
self.sp_buffer = self.spice
|
||||
|
||||
# Split up the address and convert to an int
|
||||
wl_address = int(address[self.col_addr_size:],2)
|
||||
if self.col_addr_size>0:
|
||||
col_address = int(address[0:self.col_addr_size],2)
|
||||
wl_address = int(address[self.col_addr_size:], 2)
|
||||
if self.col_addr_size > 0:
|
||||
col_address = int(address[0:self.col_addr_size], 2)
|
||||
else:
|
||||
col_address = 0
|
||||
|
||||
# 1. Keep cells in the bitcell array based on WL and BL
|
||||
wl_name = "wl_{}".format(wl_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, "* WARNING: This is a TRIMMED NETLIST.")
|
||||
|
||||
|
||||
wl_regex = r"wl\d*_{}".format(wl_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])
|
||||
|
|
@ -91,11 +92,11 @@ class trim_spice():
|
|||
#self.remove_insts("sense_amp_array",[bl_regex])
|
||||
|
||||
# 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
|
||||
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
|
||||
# Need to keep the gater too
|
||||
|
|
@ -111,7 +112,6 @@ class trim_spice():
|
|||
sp.write("\n".join(self.sp_buffer))
|
||||
sp.close()
|
||||
|
||||
|
||||
def remove_insts(self, subckt_name, keep_inst_list):
|
||||
"""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
|
||||
|
|
@ -119,7 +119,7 @@ class trim_spice():
|
|||
net connection, the instance name, anything..
|
||||
"""
|
||||
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]
|
||||
|
||||
start_name = ".SUBCKT {}".format(subckt_name)
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ def error(str, return_value=0):
|
|||
|
||||
if globals.OPTS.debug:
|
||||
pdb.set_trace()
|
||||
|
||||
|
||||
assert return_value == 0
|
||||
|
||||
|
||||
|
|
@ -108,7 +108,20 @@ def info(lev, str):
|
|||
print_raw("[{0}/{1}]: {2}".format(class_name,
|
||||
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():
|
||||
"""
|
||||
An empty function so you can set soft breakpoints in pdb.
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import getpass
|
|||
import subprocess
|
||||
|
||||
|
||||
VERSION = "1.1.13"
|
||||
VERSION = "1.1.15"
|
||||
NAME = "OpenRAM v{}".format(VERSION)
|
||||
USAGE = "openram.py [options] <config file>\nUse -h for help.\n"
|
||||
|
||||
|
|
@ -61,8 +61,13 @@ def parse_args():
|
|||
optparse.make_option("-j", "--threads",
|
||||
action="store",
|
||||
type="int",
|
||||
help="Specify the number of threads (default: 2)",
|
||||
help="Specify the number of threads (default: 1)",
|
||||
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",
|
||||
"--verbose",
|
||||
action="count",
|
||||
|
|
@ -381,6 +386,10 @@ def purge_temp():
|
|||
""" Remove the temp directory. """
|
||||
debug.info(1,
|
||||
"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
|
||||
# the directory each debug iteration
|
||||
# shutil.rmtree(OPTS.openram_temp, ignore_errors=True)
|
||||
|
|
@ -429,9 +438,15 @@ def setup_paths():
|
|||
if "__pycache__" not in full_path:
|
||||
sys.path.append("{0}".format(full_path))
|
||||
|
||||
# Use a unique temp subdirectory
|
||||
OPTS.openram_temp += "/openram_{0}_{1}_temp/".format(getpass.getuser(),
|
||||
os.getpid())
|
||||
# Use a unique temp subdirectory if multithreaded
|
||||
if OPTS.num_threads > 1 or OPTS.openram_temp == "/tmp":
|
||||
|
||||
# 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('/'):
|
||||
OPTS.openram_temp += "/"
|
||||
|
|
@ -470,6 +485,12 @@ def init_paths():
|
|||
except OSError as e:
|
||||
if e.errno == 17: # errno.EEXIST
|
||||
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!
|
||||
# make the directory if it doesn't exist
|
||||
|
|
|
|||
|
|
@ -329,13 +329,13 @@ class bank(design.design):
|
|||
self.input_control_signals = []
|
||||
port_num = 0
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
# Number of control lines in the bus for each port
|
||||
|
|
@ -530,13 +530,16 @@ class bank(design.design):
|
|||
height=self.dff.height)
|
||||
elif self.col_addr_size == 2:
|
||||
self.column_decoder = factory.create(module_type="hierarchical_predecode2x4",
|
||||
column_decoder=True,
|
||||
height=self.dff.height)
|
||||
|
||||
elif self.col_addr_size == 3:
|
||||
self.column_decoder = factory.create(module_type="hierarchical_predecode3x8",
|
||||
column_decoder=True,
|
||||
height=self.dff.height)
|
||||
elif self.col_addr_size == 4:
|
||||
self.column_decoder = factory.create(module_type="hierarchical_predecode4x16",
|
||||
column_decoder=True,
|
||||
height=self.dff.height)
|
||||
else:
|
||||
# No error checking before?
|
||||
|
|
@ -692,6 +695,8 @@ class bank(design.design):
|
|||
make_pins=(self.num_banks==1),
|
||||
pitch=self.m3_pitch)
|
||||
|
||||
self.copy_layout_pin(self.port_address_inst[0], "wl_en", self.prefix + "wl_en0")
|
||||
|
||||
# Port 1
|
||||
if len(self.all_ports)==2:
|
||||
# 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),
|
||||
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):
|
||||
""" Routing of BL and BR between port data and bitcell array """
|
||||
|
||||
|
|
@ -1055,21 +1062,6 @@ class bank(design.design):
|
|||
to_layer="m2",
|
||||
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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -64,7 +64,11 @@ class bitcell_array(bitcell_base_array):
|
|||
self.cell_inst[row, col]=self.add_inst(name=name,
|
||||
mod=self.cell)
|
||||
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):
|
||||
"""Power of Bitcell array and bitline in nW."""
|
||||
|
||||
|
|
|
|||
|
|
@ -333,21 +333,19 @@ class control_logic(design.design):
|
|||
row += 1
|
||||
self.place_gated_clk_buf_row(row)
|
||||
row += 1
|
||||
self.place_wlen_row(row)
|
||||
row += 1
|
||||
if (self.port_type == "rw") or (self.port_type == "r"):
|
||||
self.place_sen_row(row)
|
||||
row += 1
|
||||
if (self.port_type == "rw") or (self.port_type == "w"):
|
||||
self.place_wen_row(row)
|
||||
height = self.w_en_gate_inst.uy()
|
||||
control_center_y = self.w_en_gate_inst.uy()
|
||||
row += 1
|
||||
self.place_pen_row(row)
|
||||
row += 1
|
||||
if (self.port_type == "rw") or (self.port_type == "w"):
|
||||
self.place_rbl_delay_row(row)
|
||||
row += 1
|
||||
if (self.port_type == "rw") or (self.port_type == "r"):
|
||||
self.place_sen_row(row)
|
||||
row += 1
|
||||
self.place_wlen_row(row)
|
||||
row += 1
|
||||
self.place_delay(row)
|
||||
height = self.delay_inst.uy()
|
||||
control_center_y = self.delay_inst.by()
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from sram_factory import factory
|
|||
from vector import vector
|
||||
import debug
|
||||
from numpy import cumsum
|
||||
from tech import layer_properties as layer_props
|
||||
|
||||
|
||||
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)
|
||||
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 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)
|
||||
right_pin = self.local_insts[-1].get_pin(wl_name)
|
||||
self.add_layout_pin_segment_center(text=wl_name,
|
||||
layer=left_pin.layer,
|
||||
layer=wl_layer,
|
||||
start=left_pin.lc(),
|
||||
end=right_pin.rc())
|
||||
|
||||
|
|
|
|||
|
|
@ -18,19 +18,17 @@ class hierarchical_predecode(design.design):
|
|||
"""
|
||||
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
|
||||
|
||||
b = factory.create(module_type=OPTS.bitcell)
|
||||
|
||||
if not height:
|
||||
self.cell_height = b.height
|
||||
self.column_decoder = False
|
||||
else:
|
||||
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 = (height != b.height)
|
||||
|
||||
self.column_decoder = column_decoder
|
||||
|
||||
self.number_of_outputs = int(math.pow(2, self.number_of_inputs))
|
||||
super().__init__(name)
|
||||
|
|
@ -87,8 +85,14 @@ class hierarchical_predecode(design.design):
|
|||
|
||||
self.bus_layer = layer_props.hierarchical_predecode.bus_layer
|
||||
self.bus_directions = layer_props.hierarchical_predecode.bus_directions
|
||||
self.bus_pitch = getattr(self, self.bus_layer + "_pitch")
|
||||
self.bus_space = layer_props.hierarchical_predecode.bus_space_factor * getattr(self, self.bus_layer + "_space")
|
||||
if self.column_decoder:
|
||||
# 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.output_layer = layer_props.hierarchical_predecode.output_layer
|
||||
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,
|
||||
offset=offset,
|
||||
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)]
|
||||
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,
|
||||
offset=offset,
|
||||
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):
|
||||
""" Create the input inverters to invert input signals for the decode stage. """
|
||||
|
|
@ -175,10 +181,12 @@ class hierarchical_predecode(design.design):
|
|||
mirror=mirror)
|
||||
|
||||
def route(self):
|
||||
|
||||
self.route_input_inverters()
|
||||
self.route_output_inverters()
|
||||
self.route_inputs_to_rails()
|
||||
self.route_and_to_rails()
|
||||
self.route_output_and()
|
||||
self.route_input_ands()
|
||||
self.route_output_ands()
|
||||
self.route_vdd_gnd()
|
||||
|
||||
def route_inputs_to_rails(self):
|
||||
|
|
@ -210,7 +218,7 @@ class hierarchical_predecode(design.design):
|
|||
to_layer=self.bus_layer,
|
||||
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
|
||||
"""
|
||||
|
|
@ -225,12 +233,40 @@ class hierarchical_predecode(design.design):
|
|||
|
||||
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):
|
||||
out_pin = "Abar_{}".format(inv_num)
|
||||
in_pin = "in_{}".format(inv_num)
|
||||
|
||||
inv_out_pin = self.inv_inst[inv_num].get_pin("Z")
|
||||
|
||||
# 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,
|
||||
directions=self.bus_directions)
|
||||
|
||||
# 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])
|
||||
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)
|
||||
def route_input_ands(self):
|
||||
"""
|
||||
Route the different permutations of the NAND/AND decocer cells.
|
||||
"""
|
||||
|
||||
# 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
|
||||
and_input_line_combination = self.get_and_input_line_combination()
|
||||
for k in range(self.number_of_outputs):
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ class hierarchical_predecode2x4(hierarchical_predecode):
|
|||
"""
|
||||
Pre 2x4 decoder used in hierarchical_decoder.
|
||||
"""
|
||||
def __init__(self, name, height=None):
|
||||
super().__init__( name, 2, height)
|
||||
def __init__(self, name, column_decoder=False, height=None):
|
||||
super().__init__(name, 2, column_decoder, height)
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ class hierarchical_predecode3x8(hierarchical_predecode):
|
|||
"""
|
||||
Pre 3x8 decoder used in hierarchical_decoder.
|
||||
"""
|
||||
def __init__(self, name, height=None):
|
||||
super().__init__(name, 3, height)
|
||||
def __init__(self, name, column_decoder=False, height=None):
|
||||
super().__init__(name, 3, column_decoder, height)
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ class hierarchical_predecode4x16(hierarchical_predecode):
|
|||
"""
|
||||
Pre 4x16 decoder used in hierarchical_decoder.
|
||||
"""
|
||||
def __init__(self, name, height=None):
|
||||
super().__init__(name, 4, height)
|
||||
def __init__(self, name, column_decoder=False, height=None):
|
||||
super().__init__(name, 4, column_decoder, height)
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from globals import OPTS
|
|||
from sram_factory import factory
|
||||
from vector import vector
|
||||
import debug
|
||||
from tech import layer_properties as layer_props
|
||||
|
||||
|
||||
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):
|
||||
|
||||
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
|
||||
for port in self.all_ports:
|
||||
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)
|
||||
|
||||
y_offset = in_pin.cy()
|
||||
|
||||
if port == 0:
|
||||
y_offset -= 2 * self.m3_pitch
|
||||
y_offset -= global_wl_pitch_factor * global_wl_pitch
|
||||
else:
|
||||
y_offset += 2 * self.m3_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))
|
||||
|
||||
y_offset += global_wl_pitch_factor * global_wl_pitch
|
||||
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,
|
||||
to_layer="m2",
|
||||
offset=in_pin.center())
|
||||
self.add_via_center(self.m2_stack,
|
||||
offset=mid)
|
||||
|
||||
to_layer=local_wl_layer,
|
||||
offset=in_pin.center(),
|
||||
min_area=True)
|
||||
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
|
||||
for port in self.all_ports:
|
||||
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()
|
||||
mid_loc = vector(self.wl_insts[port].lx() - 1.5 * self.m3_pitch, out_loc.y)
|
||||
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):
|
||||
return self.bitcell_array_inst.by() + self.bitcell_array.get_main_array_top()
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ class options(optparse.Values):
|
|||
# This determines whether LVS and DRC is checked for every submodule.
|
||||
inline_lvsdrc = False
|
||||
# Remove noncritical memory cells for characterization speed-up
|
||||
trim_netlist = False
|
||||
trim_netlist = True
|
||||
# Run with extracted parasitics
|
||||
use_pex = False
|
||||
# Output config with all options
|
||||
|
|
@ -135,7 +135,7 @@ class options(optparse.Values):
|
|||
magic_exe = None
|
||||
|
||||
# Number of threads to use
|
||||
num_threads = 2
|
||||
num_threads = 1
|
||||
# Number of threads to use in ngspice/hspice
|
||||
num_sim_threads = 2
|
||||
|
||||
|
|
@ -155,6 +155,9 @@ class options(optparse.Values):
|
|||
# Route the input/output pins to the perimeter
|
||||
perimeter_pins = True
|
||||
|
||||
# Detailed or abstract LEF view
|
||||
detailed_lef = False
|
||||
|
||||
keep_temp = False
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -87,13 +87,11 @@ class pdriver(pgate.pgate):
|
|||
|
||||
def add_modules(self):
|
||||
self.inv_list = []
|
||||
add_well = self.add_wells
|
||||
for size in self.size_list:
|
||||
temp_inv = factory.create(module_type="pinv",
|
||||
size=size,
|
||||
height=self.height,
|
||||
add_wells=add_well)
|
||||
add_well=False
|
||||
add_wells=self.add_wells)
|
||||
self.inv_list.append(temp_inv)
|
||||
self.add_mod(temp_inv)
|
||||
|
||||
|
|
|
|||
|
|
@ -148,8 +148,9 @@ class pin_group:
|
|||
enclosure = self.router.compute_pin_enclosure(ll, ur, ll.z)
|
||||
pin_list.append(enclosure)
|
||||
|
||||
debug.check(len(pin_list) > 0,
|
||||
"Did not find any enclosures.")
|
||||
if len(pin_list) == 0:
|
||||
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
|
||||
new_pin_list = self.remove_redundant_shapes(pin_list)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class router(router_tech):
|
|||
route on a given layer. This is limited to two layer routes.
|
||||
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
|
||||
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)
|
||||
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.
|
||||
"""
|
||||
|
|
@ -99,18 +101,19 @@ class router(router_tech):
|
|||
else:
|
||||
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
|
||||
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):
|
||||
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.
|
||||
"""
|
||||
self.init_bbox(bbox)
|
||||
self.init_bbox(self.bbox, self.margin)
|
||||
self.rg = router_type(self.ll, self.ur, self.track_width)
|
||||
|
||||
def clear_pins(self):
|
||||
|
|
@ -1212,8 +1215,9 @@ class router(router_tech):
|
|||
|
||||
return None
|
||||
|
||||
def get_pin(self, pin_name):
|
||||
def get_ll_pin(self, pin_name):
|
||||
""" Return the lowest, leftest pin group """
|
||||
|
||||
keep_pin = None
|
||||
for index,pg in enumerate(self.pin_groups[pin_name]):
|
||||
for pin in pg.enclosures:
|
||||
|
|
|
|||
|
|
@ -17,12 +17,17 @@ class signal_escape_router(router):
|
|||
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
|
||||
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):
|
||||
"""
|
||||
|
|
@ -54,8 +59,8 @@ class signal_escape_router(router):
|
|||
start_time = datetime.now()
|
||||
for pin_name in ordered_pin_names:
|
||||
self.route_signal(pin_name)
|
||||
#if pin_name == "dout1[1]":
|
||||
# self.write_debug_gds("postroute.gds", False)
|
||||
# if pin_name == "dout0[1]":
|
||||
# self.write_debug_gds("postroute.gds", True)
|
||||
|
||||
print_time("Maze routing pins",datetime.now(), start_time, 3)
|
||||
|
||||
|
|
|
|||
|
|
@ -79,8 +79,8 @@ class supply_tree_router(router):
|
|||
"""
|
||||
|
||||
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,
|
||||
remaining_components))
|
||||
debug.info(1, "Routing {0} with {1} pins.".format(pin_name,
|
||||
remaining_components))
|
||||
|
||||
# Create full graph
|
||||
debug.info(2, "Creating adjacency matrix")
|
||||
|
|
@ -108,7 +108,9 @@ class supply_tree_router(router):
|
|||
connections.append((x, y))
|
||||
|
||||
# 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)
|
||||
# if pin_name == "gnd":
|
||||
# print("\nSRC {}: ".format(src) + str(self.pin_groups[pin_name][src].grids) + str(self.pin_groups[pin_name][src].blockages))
|
||||
|
|
|
|||
|
|
@ -57,11 +57,8 @@ class sram():
|
|||
if not OPTS.is_unit_test:
|
||||
print_time("SRAM creation", datetime.datetime.now(), start_time)
|
||||
|
||||
def sp_write(self, name):
|
||||
self.s.sp_write(name)
|
||||
|
||||
def lvs_write(self, name):
|
||||
self.s.lvs_write(name)
|
||||
def sp_write(self, name, lvs=False, trim=False):
|
||||
self.s.sp_write(name, lvs, trim)
|
||||
|
||||
def lef_write(self, name):
|
||||
self.s.lef_write(name)
|
||||
|
|
@ -123,7 +120,7 @@ class sram():
|
|||
start_time = datetime.datetime.now()
|
||||
lvsname = OPTS.output_path + self.s.name + ".lvs.sp"
|
||||
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:
|
||||
verify.write_lvs_script(cell_name=self.s.name,
|
||||
gds_name=os.path.basename(gdsname),
|
||||
|
|
|
|||
|
|
@ -340,6 +340,15 @@ class sram_1bank(sram_base):
|
|||
|
||||
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 = []
|
||||
|
||||
# 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]
|
||||
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
|
||||
if self.num_wmasks > 0 and port in self.write_ports:
|
||||
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:
|
||||
|
||||
# The write masks will have blockages on M1
|
||||
if self.num_wmasks > 0 and port in self.write_ports:
|
||||
layer_stack = self.m3_stack
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ class sram_base(design, verilog, lef):
|
|||
|
||||
self.add_lvs_correspondence_points()
|
||||
|
||||
# self.offset_all_coordinates()
|
||||
self.offset_all_coordinates()
|
||||
|
||||
highest_coord = self.find_highest_coords()
|
||||
self.width = highest_coord[0]
|
||||
|
|
@ -248,22 +248,33 @@ class sram_base(design, verilog, lef):
|
|||
|
||||
# Find the lowest leftest pin for vdd and 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):
|
||||
self.add_rect(pin.layer,
|
||||
pin.ll(),
|
||||
pin.width(),
|
||||
pin.height())
|
||||
# Remove the pins
|
||||
|
||||
# Remove the pin shape(s)
|
||||
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,
|
||||
pin.layer,
|
||||
pin.ll(),
|
||||
pin.width(),
|
||||
pin_offset,
|
||||
pin_width,
|
||||
pin.height())
|
||||
self.add_rect(pin.layer,
|
||||
pin_offset,
|
||||
route_width,
|
||||
pin.height())
|
||||
|
||||
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))
|
||||
|
||||
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)
|
||||
|
||||
def compute_bus_sizes(self):
|
||||
|
|
@ -675,7 +688,7 @@ class sram_base(design, verilog, lef):
|
|||
|
||||
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
|
||||
############################################################
|
||||
# Spice circuit
|
||||
|
|
@ -688,6 +701,8 @@ class sram_base(design, verilog, lef):
|
|||
sp.write("* Data bits: {}\n".format(self.word_size))
|
||||
sp.write("* Banks: {}\n".format(self.num_banks))
|
||||
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")
|
||||
# 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"],
|
||||
# spice["gnd_name"]))
|
||||
usedMODS = list()
|
||||
self.sp_write_file(sp, usedMODS, lvs_netlist=lvs_netlist)
|
||||
self.sp_write_file(sp, usedMODS, lvs=lvs, trim=trim)
|
||||
del usedMODS
|
||||
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):
|
||||
"""
|
||||
Excludes bits in column from being added to graph except target
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
@ -30,19 +30,13 @@ class timing_sram_test(openram_test):
|
|||
reload(characterizer)
|
||||
from characterizer import delay
|
||||
from sram_config import sram_config
|
||||
c = sram_config(word_size=1,
|
||||
c = sram_config(word_size=4,
|
||||
num_words=16,
|
||||
num_banks=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()
|
||||
debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank")
|
||||
s = factory.create(module_type="sram", sram_config=c)
|
||||
#import sys
|
||||
#sys.exit(1)
|
||||
|
||||
tempspice = OPTS.openram_temp + "temp.sp"
|
||||
s.sp_write(tempspice)
|
||||
|
|
@ -61,34 +55,35 @@ class timing_sram_test(openram_test):
|
|||
data.update(port_data[0])
|
||||
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
golden_data = {'min_period': 0.898,
|
||||
'write1_power': [0.2659137999999999],
|
||||
'disabled_write0_power': [0.1782495],
|
||||
'disabled_read0_power': [0.14490679999999997],
|
||||
'write0_power': [0.3330119],
|
||||
'disabled_write1_power': [0.1865223],
|
||||
'leakage_power': 0.0014532,
|
||||
'disabled_read1_power': [0.1627516],
|
||||
'slew_lh': [0.25367799999999996],
|
||||
'slew_hl': [0.25367799999999996],
|
||||
'delay_lh': [0.23820930000000004],
|
||||
'delay_hl': [0.23820930000000004],
|
||||
'read1_power': [0.3005756],
|
||||
'read0_power': [0.3005888]}
|
||||
golden_data = {'delay_hl': [0.23941909999999997],
|
||||
'delay_lh': [0.23941909999999997],
|
||||
'disabled_read0_power': [0.18183159999999998],
|
||||
'disabled_read1_power': [0.1979447],
|
||||
'disabled_write0_power': [0.2129604],
|
||||
'disabled_write1_power': [0.23266849999999997],
|
||||
'leakage_power': 0.0019882,
|
||||
'min_period': 0.938,
|
||||
'read0_power': [0.4115467],
|
||||
'read1_power': [0.41158859999999997],
|
||||
'slew_hl': [0.2798571],
|
||||
'slew_lh': [0.2798571],
|
||||
'write0_power': [0.45873749999999996],
|
||||
'write1_power': [0.40716199999999997]}
|
||||
elif OPTS.tech_name == "scn4m_subm":
|
||||
golden_data = {'leakage_power': 0.0006356576000000001,
|
||||
'write1_power': [11.292700000000002],
|
||||
'read0_power': [12.98],
|
||||
'disabled_write1_power': [8.3707],
|
||||
'write0_power': [14.4447], 'delay_hl': [1.7445000000000002],
|
||||
'disabled_read0_power': [6.4325],
|
||||
'slew_hl': [1.7437],
|
||||
'disabled_write0_power': [8.1307],
|
||||
'slew_lh': [1.7437],
|
||||
'read1_power': [12.9869],
|
||||
'disabled_read1_power': [7.706],
|
||||
'min_period': 6.25,
|
||||
'delay_lh': [1.7445000000000002]}
|
||||
golden_data = {'delay_hl': [1.7652000000000003],
|
||||
'delay_lh': [1.7652000000000003],
|
||||
'disabled_read0_power': [8.2716],
|
||||
'disabled_read1_power': [9.5857],
|
||||
'disabled_write0_power': [9.9825],
|
||||
'disabled_write1_power': [10.598400000000002],
|
||||
'leakage_power': 0.0006681718,
|
||||
'min_period': 6.562,
|
||||
'read0_power': [18.6446],
|
||||
'read1_power': [18.5126],
|
||||
'slew_hl': [1.9026],
|
||||
'slew_lh': [1.9026],
|
||||
'write0_power': [21.022600000000004],
|
||||
'write1_power': [16.6377]}
|
||||
else:
|
||||
self.assertTrue(False) # other techs fail
|
||||
# Check if no too many or too few results
|
||||
|
|
|
|||
|
|
@ -12,8 +12,7 @@ import sys, os
|
|||
sys.path.append(os.getenv("OPENRAM_HOME"))
|
||||
import globals
|
||||
from globals import OPTS
|
||||
from sram_factory import factory
|
||||
import debug
|
||||
|
||||
|
||||
class timing_setup_test(openram_test):
|
||||
|
||||
|
|
@ -29,14 +28,12 @@ class timing_setup_test(openram_test):
|
|||
import characterizer
|
||||
reload(characterizer)
|
||||
from characterizer import setup_hold
|
||||
import sram
|
||||
import tech
|
||||
slews = [tech.spice["rise_time"]*2]
|
||||
|
||||
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
|
||||
sh = setup_hold(corner)
|
||||
data = sh.analyze(slews,slews)
|
||||
#print data
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
golden_data = {'hold_times_HL': [-0.0158691],
|
||||
'hold_times_LH': [-0.0158691],
|
||||
|
|
@ -47,6 +44,11 @@ class timing_setup_test(openram_test):
|
|||
'hold_times_LH': [-0.11718749999999999],
|
||||
'setup_times_HL': [0.16357419999999998],
|
||||
'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:
|
||||
self.assertTrue(False) # other techs fail
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class timing_sram_test(openram_test):
|
|||
reload(characterizer)
|
||||
from characterizer import delay
|
||||
from sram_config import sram_config
|
||||
c = sram_config(word_size=1,
|
||||
c = sram_config(word_size=4,
|
||||
num_words=16,
|
||||
num_banks=1)
|
||||
c.words_per_row=1
|
||||
|
|
@ -55,35 +55,35 @@ class timing_sram_test(openram_test):
|
|||
data.update(port_data[0])
|
||||
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
golden_data = {'slew_lh': [0.2592187],
|
||||
'slew_hl': [0.2592187],
|
||||
'delay_lh': [0.2465583],
|
||||
'disabled_write0_power': [0.1924678],
|
||||
'disabled_read0_power': [0.152483],
|
||||
'write0_power': [0.3409064],
|
||||
'disabled_read1_power': [0.1737818],
|
||||
'read0_power': [0.3096708],
|
||||
'read1_power': [0.3107916],
|
||||
'delay_hl': [0.2465583],
|
||||
'write1_power': [0.26915849999999997],
|
||||
'leakage_power': 0.002044307,
|
||||
'min_period': 0.898,
|
||||
'disabled_write1_power': [0.201411]}
|
||||
golden_data = {'delay_hl': [0.24671600000000002],
|
||||
'delay_lh': [0.24671600000000002],
|
||||
'disabled_read0_power': [0.1749204],
|
||||
'disabled_read1_power': [0.1873704],
|
||||
'disabled_write0_power': [0.204619],
|
||||
'disabled_write1_power': [0.2262653],
|
||||
'leakage_power': 0.0021375310000000002,
|
||||
'min_period': 0.977,
|
||||
'read0_power': [0.3856875],
|
||||
'read1_power': [0.38856060000000003],
|
||||
'slew_hl': [0.2842019],
|
||||
'slew_lh': [0.2842019],
|
||||
'write0_power': [0.45274410000000004],
|
||||
'write1_power': [0.38727789999999995]}
|
||||
elif OPTS.tech_name == "scn4m_subm":
|
||||
golden_data = {'delay_hl': [1.8435739999999998],
|
||||
'delay_lh': [1.8435739999999998],
|
||||
'disabled_read0_power': [5.917947],
|
||||
'disabled_read1_power': [7.154297],
|
||||
'disabled_write0_power': [7.696351],
|
||||
'disabled_write1_power': [7.999409000000001],
|
||||
'leakage_power': 0.004809726,
|
||||
'min_period': 6.875,
|
||||
'read0_power': [11.833079999999999],
|
||||
'read1_power': [11.99236],
|
||||
'slew_hl': [1.8668490000000002],
|
||||
'slew_lh': [1.8668490000000002],
|
||||
'write0_power': [13.287510000000001],
|
||||
'write1_power': [10.416369999999999]}
|
||||
golden_data = {'delay_hl': [1.882508],
|
||||
'delay_lh': [1.882508],
|
||||
'disabled_read0_power': [7.487227],
|
||||
'disabled_read1_power': [8.749013],
|
||||
'disabled_write0_power': [9.268901],
|
||||
'disabled_write1_power': [9.962973],
|
||||
'leakage_power': 0.0046686359999999994,
|
||||
'min_period': 7.188,
|
||||
'read0_power': [16.64011],
|
||||
'read1_power': [17.20825],
|
||||
'slew_hl': [2.039655],
|
||||
'slew_lh': [2.039655],
|
||||
'write0_power': [19.31883],
|
||||
'write1_power': [15.297369999999999]}
|
||||
else:
|
||||
self.assertTrue(False) # other techs fail
|
||||
|
||||
|
|
|
|||
|
|
@ -12,8 +12,7 @@ import sys, os
|
|||
sys.path.append(os.getenv("OPENRAM_HOME"))
|
||||
import globals
|
||||
from globals import OPTS
|
||||
from sram_factory import factory
|
||||
import debug
|
||||
|
||||
|
||||
class timing_setup_test(openram_test):
|
||||
|
||||
|
|
@ -29,14 +28,12 @@ class timing_setup_test(openram_test):
|
|||
import characterizer
|
||||
reload(characterizer)
|
||||
from characterizer import setup_hold
|
||||
import sram
|
||||
import tech
|
||||
slews = [tech.spice["rise_time"]*2]
|
||||
|
||||
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
|
||||
sh = setup_hold(corner)
|
||||
data = sh.analyze(slews,slews)
|
||||
#print data
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
golden_data = {'hold_times_HL': [-0.01586914],
|
||||
'hold_times_LH': [-0.01586914],
|
||||
|
|
@ -47,6 +44,11 @@ class timing_setup_test(openram_test):
|
|||
'hold_times_LH': [-0.1293945],
|
||||
'setup_times_HL': [0.1757812],
|
||||
'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:
|
||||
self.assertTrue(False) # other techs fail
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class psram_1bank_2mux_func_test(openram_test):
|
|||
OPTS.analytical_delay = False
|
||||
OPTS.netlist_only = True
|
||||
OPTS.trim_netlist = False
|
||||
|
||||
|
||||
OPTS.bitcell = "pbitcell"
|
||||
OPTS.replica_bitcell="replica_pbitcell"
|
||||
OPTS.dummy_bitcell="dummy_pbitcell"
|
||||
|
|
@ -53,10 +53,7 @@ class psram_1bank_2mux_func_test(openram_test):
|
|||
c.words_per_row,
|
||||
c.num_banks))
|
||||
s = factory.create(module_type="sram", sram_config=c)
|
||||
tempspice = OPTS.openram_temp + "sram.sp"
|
||||
s.sp_write(tempspice)
|
||||
|
||||
f = functional(s.s, tempspice)
|
||||
f = functional(s.s)
|
||||
(fail, error) = f.run()
|
||||
self.assertTrue(fail, error)
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class psram_1bank_4mux_func_test(openram_test):
|
|||
OPTS.analytical_delay = False
|
||||
OPTS.netlist_only = True
|
||||
OPTS.trim_netlist = False
|
||||
|
||||
|
||||
OPTS.bitcell = "pbitcell"
|
||||
OPTS.replica_bitcell="replica_pbitcell"
|
||||
OPTS.dummy_bitcell="dummy_pbitcell"
|
||||
|
|
@ -54,11 +54,8 @@ class psram_1bank_4mux_func_test(openram_test):
|
|||
c.words_per_row,
|
||||
c.num_banks))
|
||||
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])
|
||||
f = functional(s.s, tempspice, corner)
|
||||
f = functional(s.s, corner=corner)
|
||||
(fail, error) = f.run()
|
||||
self.assertTrue(fail, error)
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class psram_1bank_8mux_func_test(openram_test):
|
|||
OPTS.analytical_delay = False
|
||||
OPTS.netlist_only = True
|
||||
OPTS.trim_netlist = False
|
||||
|
||||
|
||||
OPTS.bitcell = "pbitcell"
|
||||
OPTS.replica_bitcell="replica_pbitcell"
|
||||
OPTS.dummy_bitcell="dummy_pbitcell"
|
||||
|
|
@ -54,10 +54,7 @@ class psram_1bank_8mux_func_test(openram_test):
|
|||
c.words_per_row,
|
||||
c.num_banks))
|
||||
s = factory.create(module_type="sram", sram_config=c)
|
||||
tempspice = OPTS.openram_temp + "sram.sp"
|
||||
s.sp_write(tempspice)
|
||||
|
||||
f = functional(s.s, tempspice)
|
||||
f = functional(s.s)
|
||||
(fail, error) = f.run()
|
||||
self.assertTrue(fail, error)
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class psram_1bank_nomux_func_test(openram_test):
|
|||
OPTS.analytical_delay = False
|
||||
OPTS.netlist_only = True
|
||||
OPTS.trim_netlist = False
|
||||
|
||||
|
||||
OPTS.bitcell = "pbitcell"
|
||||
OPTS.replica_bitcell="replica_pbitcell"
|
||||
OPTS.dummy_bitcell="dummy_pbitcell"
|
||||
|
|
@ -53,10 +53,7 @@ class psram_1bank_nomux_func_test(openram_test):
|
|||
c.words_per_row,
|
||||
c.num_banks))
|
||||
s = factory.create(module_type="sram", sram_config=c)
|
||||
tempspice = OPTS.openram_temp + "sram.sp"
|
||||
s.sp_write(tempspice)
|
||||
|
||||
f = functional(s.s, tempspice)
|
||||
f = functional(s.s)
|
||||
(fail, error) = f.run()
|
||||
self.assertTrue(fail, error)
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class sram_1bank_2mux_func_test(openram_test):
|
|||
OPTS.analytical_delay = False
|
||||
OPTS.netlist_only = True
|
||||
OPTS.trim_netlist = False
|
||||
|
||||
|
||||
# This is a hack to reload the characterizer __init__ with the spice version
|
||||
from importlib import reload
|
||||
import characterizer
|
||||
|
|
@ -43,10 +43,7 @@ class sram_1bank_2mux_func_test(openram_test):
|
|||
c.words_per_row,
|
||||
c.num_banks))
|
||||
s = factory.create(module_type="sram", sram_config=c)
|
||||
tempspice = OPTS.openram_temp + "sram.sp"
|
||||
s.sp_write(tempspice)
|
||||
|
||||
f = functional(s.s, tempspice)
|
||||
f = functional(s.s)
|
||||
(fail, error) = f.run()
|
||||
self.assertTrue(fail, error)
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class sram_1bank_2mux_func_test(openram_test):
|
|||
OPTS.analytical_delay = False
|
||||
OPTS.netlist_only = True
|
||||
OPTS.trim_netlist = False
|
||||
|
||||
|
||||
# This is a hack to reload the characterizer __init__ with the spice version
|
||||
from importlib import reload
|
||||
import characterizer
|
||||
|
|
@ -44,10 +44,7 @@ class sram_1bank_2mux_func_test(openram_test):
|
|||
c.words_per_row,
|
||||
c.num_banks))
|
||||
s = factory.create(module_type="sram", sram_config=c)
|
||||
tempspice = OPTS.openram_temp + "sram.sp"
|
||||
s.sp_write(tempspice)
|
||||
|
||||
f = functional(s.s, tempspice)
|
||||
f = functional(s.s)
|
||||
(fail, error) = f.run()
|
||||
self.assertTrue(fail, error)
|
||||
|
||||
|
|
|
|||
|
|
@ -45,10 +45,7 @@ class sram_1bank_2mux_sparecols_func_test(openram_test):
|
|||
c.num_spare_cols,
|
||||
c.num_banks))
|
||||
s = factory.create(module_type="sram", sram_config=c)
|
||||
tempspice = OPTS.openram_temp + "sram.sp"
|
||||
s.sp_write(tempspice)
|
||||
|
||||
f = functional(s.s, tempspice)
|
||||
f = functional(s.s)
|
||||
(fail, error) = f.run()
|
||||
self.assertTrue(fail, error)
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class sram_1bank_4mux_func_test(openram_test):
|
|||
OPTS.analytical_delay = False
|
||||
OPTS.netlist_only = True
|
||||
OPTS.trim_netlist = False
|
||||
|
||||
|
||||
# This is a hack to reload the characterizer __init__ with the spice version
|
||||
from importlib import reload
|
||||
import characterizer
|
||||
|
|
@ -43,10 +43,7 @@ class sram_1bank_4mux_func_test(openram_test):
|
|||
c.words_per_row,
|
||||
c.num_banks))
|
||||
s = factory.create(module_type="sram", sram_config=c)
|
||||
tempspice = OPTS.openram_temp + "sram.sp"
|
||||
s.sp_write(tempspice)
|
||||
|
||||
f = functional(s.s, tempspice)
|
||||
f = functional(s.s)
|
||||
(fail, error) = f.run()
|
||||
self.assertTrue(fail, error)
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class sram_1bank_8mux_func_test(openram_test):
|
|||
OPTS.analytical_delay = False
|
||||
OPTS.netlist_only = True
|
||||
OPTS.trim_netlist = False
|
||||
|
||||
|
||||
# This is a hack to reload the characterizer __init__ with the spice version
|
||||
from importlib import reload
|
||||
import characterizer
|
||||
|
|
@ -46,10 +46,7 @@ class sram_1bank_8mux_func_test(openram_test):
|
|||
c.words_per_row,
|
||||
c.num_banks))
|
||||
s = factory.create(module_type="sram", sram_config=c)
|
||||
tempspice = OPTS.openram_temp + "sram.sp"
|
||||
s.sp_write(tempspice)
|
||||
|
||||
f = functional(s.s, tempspice)
|
||||
f = functional(s.s)
|
||||
(fail, error) = f.run()
|
||||
self.assertTrue(fail, error)
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class psram_1bank_nomux_func_test(openram_test):
|
|||
OPTS.analytical_delay = False
|
||||
OPTS.netlist_only = True
|
||||
OPTS.trim_netlist = False
|
||||
|
||||
OPTS.num_rw_ports = 1
|
||||
OPTS.num_w_ports = 0
|
||||
OPTS.num_r_ports = 1
|
||||
|
|
@ -46,10 +47,7 @@ class psram_1bank_nomux_func_test(openram_test):
|
|||
c.words_per_row,
|
||||
c.num_banks))
|
||||
s = factory.create(module_type="sram", sram_config=c)
|
||||
tempspice = OPTS.openram_temp + "sram.sp"
|
||||
s.sp_write(tempspice)
|
||||
|
||||
f = functional(s.s, tempspice)
|
||||
f = functional(s.s)
|
||||
(fail, error) = f.run()
|
||||
self.assertTrue(fail, error)
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class sram_1bank_nomux_func_test(openram_test):
|
|||
globals.init_openram(config_file)
|
||||
OPTS.analytical_delay = False
|
||||
OPTS.netlist_only = True
|
||||
OPTS.trim_netlist = False
|
||||
|
||||
# This is a hack to reload the characterizer __init__ with the spice version
|
||||
from importlib import reload
|
||||
|
|
@ -42,10 +43,7 @@ class sram_1bank_nomux_func_test(openram_test):
|
|||
c.words_per_row,
|
||||
c.num_banks))
|
||||
s = factory.create(module_type="sram", sram_config=c)
|
||||
tempspice = OPTS.openram_temp + "sram.sp"
|
||||
s.sp_write(tempspice)
|
||||
|
||||
f = functional(s.s, tempspice)
|
||||
f = functional(s.s)
|
||||
(fail, error) = f.run()
|
||||
self.assertTrue(fail, error)
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class sram_1bank_nomux_sparecols_func_test(openram_test):
|
|||
globals.init_openram(config_file)
|
||||
OPTS.analytical_delay = False
|
||||
OPTS.netlist_only = True
|
||||
OPTS.trim_netlist = False
|
||||
|
||||
# This is a hack to reload the characterizer __init__ with the spice version
|
||||
from importlib import reload
|
||||
|
|
@ -43,10 +44,7 @@ class sram_1bank_nomux_sparecols_func_test(openram_test):
|
|||
c.words_per_row,
|
||||
c.num_banks))
|
||||
s = factory.create(module_type="sram", sram_config=c)
|
||||
tempspice = OPTS.openram_temp + "sram.sp"
|
||||
s.sp_write(tempspice)
|
||||
|
||||
f = functional(s.s, tempspice)
|
||||
f = functional(s.s)
|
||||
(fail, error) = f.run()
|
||||
self.assertTrue(fail, error)
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class sram_wmask_1w_1r_func_test(openram_test):
|
|||
OPTS.analytical_delay = False
|
||||
OPTS.netlist_only = True
|
||||
OPTS.trim_netlist = False
|
||||
|
||||
OPTS.num_rw_ports = 1
|
||||
OPTS.num_w_ports = 0
|
||||
OPTS.num_r_ports = 1
|
||||
|
|
@ -49,10 +50,7 @@ class sram_wmask_1w_1r_func_test(openram_test):
|
|||
c.write_size,
|
||||
c.num_banks))
|
||||
s = factory.create(module_type="sram", sram_config=c)
|
||||
tempspice = OPTS.openram_temp + "sram.sp"
|
||||
s.sp_write(tempspice)
|
||||
|
||||
f = functional(s.s, tempspice)
|
||||
f = functional(s.s)
|
||||
(fail, error) = f.run()
|
||||
self.assertTrue(fail, error)
|
||||
|
||||
|
|
|
|||
|
|
@ -45,10 +45,7 @@ class sram_wmask_func_test(openram_test):
|
|||
c.write_size,
|
||||
c.num_banks))
|
||||
s = factory.create(module_type="sram", sram_config=c)
|
||||
tempspice = OPTS.openram_temp + "sram.sp"
|
||||
s.sp_write(tempspice)
|
||||
|
||||
f = functional(s.s, tempspice)
|
||||
f = functional(s.s)
|
||||
(fail, error) = f.run()
|
||||
self.assertTrue(fail, error)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ class lib_test(openram_test):
|
|||
globals.init_openram(config_file)
|
||||
OPTS.analytical_delay = False
|
||||
OPTS.netlist_only = True
|
||||
OPTS.trim_netlist = False
|
||||
|
||||
# This is a hack to reload the characterizer __init__ with the spice version
|
||||
from importlib import reload
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class sram_pex_test(openram_test):
|
|||
tempspice = self.run_pex(s)
|
||||
|
||||
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()
|
||||
self.assertTrue(fail, error)
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ class openram_back_end_test(openram_test):
|
|||
if OPTS.tech_name:
|
||||
options += " -t {}".format(OPTS.tech_name)
|
||||
|
||||
options += " -j 2"
|
||||
|
||||
# Always perform code coverage
|
||||
if OPTS.coverage == 0:
|
||||
debug.warning("Failed to find coverage installation. This can be installed with pip3 install coverage")
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ class openram_front_end_test(openram_test):
|
|||
if OPTS.tech_name:
|
||||
options += " -t {}".format(OPTS.tech_name)
|
||||
|
||||
options += " -j 2"
|
||||
|
||||
# Always perform code coverage
|
||||
if OPTS.coverage == 0:
|
||||
debug.warning("Failed to find coverage installation. This can be installed with pip3 install coverage")
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ class riscv_func_test(openram_test):
|
|||
globals.init_openram(config_file)
|
||||
OPTS.analytical_delay = False
|
||||
OPTS.netlist_only = True
|
||||
OPTS.trim_netlist = False
|
||||
OPTS.local_array_size = 16
|
||||
OPTS.num_rw_ports = 1
|
||||
OPTS.num_w_ports = 0
|
||||
|
|
@ -49,11 +48,8 @@ class riscv_func_test(openram_test):
|
|||
c.words_per_row,
|
||||
c.num_banks))
|
||||
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])
|
||||
f = functional(s.s, tempspice, corner)
|
||||
f = functional(s.s, corner=corner)
|
||||
(fail, error) = f.run()
|
||||
self.assertTrue(fail, error)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ module sram_2_16_1_freepdk45(
|
|||
parameter RAM_DEPTH = 1 << ADDR_WIDTH;
|
||||
// FIXME: This delay is arbitrary.
|
||||
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 csb0; // active low chip select
|
||||
|
|
@ -33,10 +35,10 @@ module sram_2_16_1_freepdk45(
|
|||
web0_reg = web0;
|
||||
addr0_reg = addr0;
|
||||
din0_reg = din0;
|
||||
dout0 = 2'bx;
|
||||
if ( !csb0_reg && web0_reg )
|
||||
#(T_HOLD) dout0 = 2'bx;
|
||||
if ( !csb0_reg && web0_reg && VERBOSE )
|
||||
$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);
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ module sram_2_16_1_scn4m_subm(
|
|||
parameter RAM_DEPTH = 1 << ADDR_WIDTH;
|
||||
// FIXME: This delay is arbitrary.
|
||||
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 csb0; // active low chip select
|
||||
|
|
@ -33,10 +35,10 @@ module sram_2_16_1_scn4m_subm(
|
|||
web0_reg = web0;
|
||||
addr0_reg = addr0;
|
||||
din0_reg = din0;
|
||||
dout0 = 2'bx;
|
||||
if ( !csb0_reg && web0_reg )
|
||||
#(T_HOLD) dout0 = 2'bx;
|
||||
if ( !csb0_reg && web0_reg && VERBOSE )
|
||||
$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);
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import sys, os
|
|||
sys.path.append(os.getenv("OPENRAM_HOME"))
|
||||
import globals
|
||||
from subunit import ProtocolTestCase, TestProtocolClient
|
||||
from subunit.test_results import AutoTimingTestResultDecorator
|
||||
from testtools import ConcurrentTestSuite
|
||||
|
||||
(OPTS, args) = globals.parse_args()
|
||||
|
|
@ -71,7 +70,7 @@ def fork_tests(num_threads):
|
|||
stream = os.fdopen(c2pwrite, 'wb', 0)
|
||||
os.close(c2pread)
|
||||
sys.stdin.close()
|
||||
test_suite_result = AutoTimingTestResultDecorator(TestProtocolClient(stream))
|
||||
test_suite_result = TestProtocolClient(stream)
|
||||
test_suite.run(test_suite_result)
|
||||
except EBADF:
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -12,11 +12,36 @@ from globals import OPTS
|
|||
import debug
|
||||
import pdb
|
||||
import traceback
|
||||
import time
|
||||
|
||||
|
||||
class openram_test(unittest.TestCase):
|
||||
""" 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):
|
||||
|
||||
self.reset()
|
||||
|
|
@ -28,10 +53,9 @@ class openram_test(unittest.TestCase):
|
|||
result=verify.run_drc(w.name, tempgds, None)
|
||||
if result != 0:
|
||||
self.fail("DRC failed: {}".format(w.name))
|
||||
|
||||
if not OPTS.keep_temp:
|
||||
elif not OPTS.keep_temp:
|
||||
self.cleanup()
|
||||
|
||||
|
||||
def local_check(self, a, final_verification=False):
|
||||
|
||||
self.reset()
|
||||
|
|
@ -39,7 +63,7 @@ class openram_test(unittest.TestCase):
|
|||
tempspice = "{}.sp".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
|
||||
if not OPTS.netlist_only:
|
||||
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)
|
||||
self.fail("LVS mismatch: {}".format(a.name))
|
||||
|
||||
if lvs_result == 0 and drc_result == 0 and not OPTS.keep_temp:
|
||||
self.cleanup()
|
||||
# For debug...
|
||||
# import pdb; pdb.set_trace()
|
||||
if not OPTS.keep_temp:
|
||||
self.cleanup()
|
||||
|
||||
def run_pex(self, a, output=None):
|
||||
tempspice = "{}.sp".format(a.name)
|
||||
|
|
@ -104,6 +128,7 @@ class openram_test(unittest.TestCase):
|
|||
|
||||
def cleanup(self):
|
||||
""" Reset the duplicate checker and cleanup files. """
|
||||
|
||||
files = glob.glob(OPTS.openram_temp + '*')
|
||||
for f in files:
|
||||
# Only remove the files
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
|
|
@ -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 |
Loading…
Reference in New Issue