diff --git a/.gitattributes b/.gitattributes index 2e6daefb..23fcc05d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ -*.sp linguist-vendored +*.sp linguist-language=Spice +*.tf linquist-language=Tech File diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 262e65ca..ef2ef6a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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] diff --git a/README.md b/README.md index da68361b..674d13f2 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,9 @@ +![](./images/OpenRAM_logo_yellow_transparent.svg) # OpenRAM [![Python 3.5](https://img.shields.io/badge/Python-3.5-green.svg)](https://www.python.org/) [![License: BSD 3-clause](./images/license_badge.svg)](./LICENSE) - -Master: -[![Download](./images/download-stable-blue.svg)](https://github.com/VLSIDA/OpenRAM/archive/master.zip) - -Dev: +[![Download](./images/download-stable-blue.svg)](https://github.com/VLSIDA/OpenRAM/archive/stable.zip) [![Download](./images/download-unstable-blue.svg)](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 diff --git a/compiler/base/custom_layer_properties.py b/compiler/base/custom_layer_properties.py index 7f8e5993..eff24f82 100644 --- a/compiler/base/custom_layer_properties.py +++ b/compiler/base/custom_layer_properties.py @@ -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 + diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index fe1f4c55..d99c7363 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -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) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index fad5c8ae..1e2add8d 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -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, diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index 51d2c3b7..2f2d3ec9 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -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() diff --git a/compiler/base/lef.py b/compiler/base/lef.py index 942a8ac2..9ff02816 100644 --- a/compiler/base/lef.py +++ b/compiler/base/lef.py @@ -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") diff --git a/compiler/base/pin_layout.py b/compiler/base/pin_layout.py index eb4cb2ad..e6baa4fc 100644 --- a/compiler/base/pin_layout.py +++ b/compiler/base/pin_layout.py @@ -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. diff --git a/compiler/base/verilog.py b/compiler/base/verilog.py index ef0550c6..0405649d 100644 --- a/compiler/base/verilog.py +++ b/compiler/base/verilog.py @@ -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: diff --git a/compiler/characterizer/charutils.py b/compiler/characterizer/charutils.py index b618f0db..8f33c279 100644 --- a/compiler/characterizer/charutils.py +++ b/compiler/characterizer/charutils.py @@ -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) diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index f491d8b0..168a73e9 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -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) diff --git a/compiler/characterizer/elmore.py b/compiler/characterizer/elmore.py index b9f99f02..3d897f80 100644 --- a/compiler/characterizer/elmore.py +++ b/compiler/characterizer/elmore.py @@ -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. diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index fb0a176f..f93de85c 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -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) diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py index d37119a9..aa892b3d 100644 --- a/compiler/characterizer/lib.py +++ b/compiler/characterizer/lib.py @@ -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)) - \ No newline at end of file + diff --git a/compiler/characterizer/setup_hold.py b/compiler/characterizer/setup_hold.py index 83ec835b..b323078a 100644 --- a/compiler/characterizer/setup_hold.py +++ b/compiler/characterizer/setup_hold.py @@ -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 diff --git a/compiler/characterizer/simulation.py b/compiler/characterizer/simulation.py index 0afe2459..5becbacf 100644 --- a/compiler/characterizer/simulation.py +++ b/compiler/characterizer/simulation.py @@ -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 diff --git a/compiler/characterizer/stimuli.py b/compiler/characterizer/stimuli.py index 4eee9e5c..d60cab85 100644 --- a/compiler/characterizer/stimuli.py +++ b/compiler/characterizer/stimuli.py @@ -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, diff --git a/compiler/characterizer/trim_spice.py b/compiler/characterizer/trim_spice.py index d659212c..e8499d5c 100644 --- a/compiler/characterizer/trim_spice.py +++ b/compiler/characterizer/trim_spice.py @@ -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) diff --git a/compiler/debug.py b/compiler/debug.py index f14b1224..f8a4807d 100644 --- a/compiler/debug.py +++ b/compiler/debug.py @@ -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. diff --git a/compiler/example_configs/riscv_scn4m_subm_2skbyte_1rw1r.py b/compiler/example_configs/riscv_scn4m_subm_2kbyte_1rw1r.py similarity index 100% rename from compiler/example_configs/riscv_scn4m_subm_2skbyte_1rw1r.py rename to compiler/example_configs/riscv_scn4m_subm_2kbyte_1rw1r.py diff --git a/compiler/globals.py b/compiler/globals.py index b977e8e3..68d055ff 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -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] \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 diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index ce545b10..1f06aef3 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -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): """ diff --git a/compiler/modules/bitcell_array.py b/compiler/modules/bitcell_array.py index c7d8ff81..9d1cc0de 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -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 col0 and row 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) diff --git a/compiler/router/router.py b/compiler/router/router.py index dc0c8e8d..5213af4a 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -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: diff --git a/compiler/router/signal_escape_router.py b/compiler/router/signal_escape_router.py index 846925bd..a9531290 100644 --- a/compiler/router/signal_escape_router.py +++ b/compiler/router/signal_escape_router.py @@ -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) diff --git a/compiler/router/supply_tree_router.py b/compiler/router/supply_tree_router.py index 0b29119d..ba54ee39 100644 --- a/compiler/router/supply_tree_router.py +++ b/compiler/router/supply_tree_router.py @@ -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)) diff --git a/compiler/sram/sram.py b/compiler/sram/sram.py index fa7f202f..599ef666 100644 --- a/compiler/sram/sram.py +++ b/compiler/sram/sram.py @@ -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), diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index d831c047..6bc2cd43 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -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: diff --git a/compiler/sram/sram_base.py b/compiler/sram/sram_base.py index 868b2059..7621f67b 100644 --- a/compiler/sram/sram_base.py +++ b/compiler/sram/sram_base.py @@ -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 diff --git a/compiler/tests/20_sram_1bank_4mux_1rw_1r_test.py b/compiler/tests/20_sram_1bank_4mux_1rw_1r_test.py new file mode 100755 index 00000000..8bcc0415 --- /dev/null +++ b/compiler/tests/20_sram_1bank_4mux_1rw_1r_test.py @@ -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()) diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py index 7ca1ad33..6a5e8f52 100755 --- a/compiler/tests/21_hspice_delay_test.py +++ b/compiler/tests/21_hspice_delay_test.py @@ -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 diff --git a/compiler/tests/21_hspice_setuphold_test.py b/compiler/tests/21_hspice_setuphold_test.py index e97a8aca..634b2982 100755 --- a/compiler/tests/21_hspice_setuphold_test.py +++ b/compiler/tests/21_hspice_setuphold_test.py @@ -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 diff --git a/compiler/tests/21_ngspice_delay_test.py b/compiler/tests/21_ngspice_delay_test.py index f9570c2b..9a0d224c 100755 --- a/compiler/tests/21_ngspice_delay_test.py +++ b/compiler/tests/21_ngspice_delay_test.py @@ -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 diff --git a/compiler/tests/21_ngspice_setuphold_test.py b/compiler/tests/21_ngspice_setuphold_test.py index da6d07ff..dab02e7d 100755 --- a/compiler/tests/21_ngspice_setuphold_test.py +++ b/compiler/tests/21_ngspice_setuphold_test.py @@ -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 diff --git a/compiler/tests/22_psram_1bank_2mux_func_test.py b/compiler/tests/22_psram_1bank_2mux_func_test.py index ea1c5d22..f5f4cd8f 100755 --- a/compiler/tests/22_psram_1bank_2mux_func_test.py +++ b/compiler/tests/22_psram_1bank_2mux_func_test.py @@ -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) diff --git a/compiler/tests/22_psram_1bank_4mux_func_test.py b/compiler/tests/22_psram_1bank_4mux_func_test.py index f82d89d9..f422728b 100755 --- a/compiler/tests/22_psram_1bank_4mux_func_test.py +++ b/compiler/tests/22_psram_1bank_4mux_func_test.py @@ -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) diff --git a/compiler/tests/22_psram_1bank_8mux_func_test.py b/compiler/tests/22_psram_1bank_8mux_func_test.py index d333b604..f0247808 100755 --- a/compiler/tests/22_psram_1bank_8mux_func_test.py +++ b/compiler/tests/22_psram_1bank_8mux_func_test.py @@ -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) diff --git a/compiler/tests/22_psram_1bank_nomux_func_test.py b/compiler/tests/22_psram_1bank_nomux_func_test.py index 2cd776e9..203fd0ac 100755 --- a/compiler/tests/22_psram_1bank_nomux_func_test.py +++ b/compiler/tests/22_psram_1bank_nomux_func_test.py @@ -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) diff --git a/compiler/tests/22_sram_1bank_2mux_func_test.py b/compiler/tests/22_sram_1bank_2mux_func_test.py index 8d0b9af3..cca6aeeb 100755 --- a/compiler/tests/22_sram_1bank_2mux_func_test.py +++ b/compiler/tests/22_sram_1bank_2mux_func_test.py @@ -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) diff --git a/compiler/tests/22_sram_1bank_2mux_global_func_test.py b/compiler/tests/22_sram_1bank_2mux_global_func_test.py index 3a085adc..14de7ba8 100755 --- a/compiler/tests/22_sram_1bank_2mux_global_func_test.py +++ b/compiler/tests/22_sram_1bank_2mux_global_func_test.py @@ -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) diff --git a/compiler/tests/22_sram_1bank_2mux_sparecols_func_test.py b/compiler/tests/22_sram_1bank_2mux_sparecols_func_test.py index 9d797564..c84c70a5 100755 --- a/compiler/tests/22_sram_1bank_2mux_sparecols_func_test.py +++ b/compiler/tests/22_sram_1bank_2mux_sparecols_func_test.py @@ -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) diff --git a/compiler/tests/22_sram_1bank_4mux_func_test.py b/compiler/tests/22_sram_1bank_4mux_func_test.py index 403acdf0..4f7035cc 100755 --- a/compiler/tests/22_sram_1bank_4mux_func_test.py +++ b/compiler/tests/22_sram_1bank_4mux_func_test.py @@ -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) diff --git a/compiler/tests/22_sram_1bank_8mux_func_test.py b/compiler/tests/22_sram_1bank_8mux_func_test.py index c4684ccb..cc6456cf 100755 --- a/compiler/tests/22_sram_1bank_8mux_func_test.py +++ b/compiler/tests/22_sram_1bank_8mux_func_test.py @@ -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) diff --git a/compiler/tests/22_sram_1bank_nomux_1rw_1r_func_test.py b/compiler/tests/22_sram_1bank_nomux_1rw_1r_func_test.py index ffe03906..c0518957 100755 --- a/compiler/tests/22_sram_1bank_nomux_1rw_1r_func_test.py +++ b/compiler/tests/22_sram_1bank_nomux_1rw_1r_func_test.py @@ -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) diff --git a/compiler/tests/22_sram_1bank_nomux_func_test.py b/compiler/tests/22_sram_1bank_nomux_func_test.py index b2f28891..ff33ead5 100755 --- a/compiler/tests/22_sram_1bank_nomux_func_test.py +++ b/compiler/tests/22_sram_1bank_nomux_func_test.py @@ -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) diff --git a/compiler/tests/22_sram_1bank_nomux_sparecols_func_test.py b/compiler/tests/22_sram_1bank_nomux_sparecols_func_test.py index d1455aec..0d649ace 100755 --- a/compiler/tests/22_sram_1bank_nomux_sparecols_func_test.py +++ b/compiler/tests/22_sram_1bank_nomux_sparecols_func_test.py @@ -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) diff --git a/compiler/tests/22_sram_1bank_wmask_1rw_1r_func_test.py b/compiler/tests/22_sram_1bank_wmask_1rw_1r_func_test.py index 317559d0..6808462e 100755 --- a/compiler/tests/22_sram_1bank_wmask_1rw_1r_func_test.py +++ b/compiler/tests/22_sram_1bank_wmask_1rw_1r_func_test.py @@ -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) diff --git a/compiler/tests/22_sram_wmask_func_test.py b/compiler/tests/22_sram_wmask_func_test.py index d1dea6a9..f41b3d98 100755 --- a/compiler/tests/22_sram_wmask_func_test.py +++ b/compiler/tests/22_sram_wmask_func_test.py @@ -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) diff --git a/compiler/tests/23_lib_sram_test.py b/compiler/tests/23_lib_sram_test.py index c362c559..a894b328 100755 --- a/compiler/tests/23_lib_sram_test.py +++ b/compiler/tests/23_lib_sram_test.py @@ -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 diff --git a/compiler/tests/26_sram_pex_test.py b/compiler/tests/26_sram_pex_test.py index 4e889eee..90dd46ee 100755 --- a/compiler/tests/26_sram_pex_test.py +++ b/compiler/tests/26_sram_pex_test.py @@ -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) diff --git a/compiler/tests/30_openram_back_end_test.py b/compiler/tests/30_openram_back_end_test.py index c2060656..c67b8249 100755 --- a/compiler/tests/30_openram_back_end_test.py +++ b/compiler/tests/30_openram_back_end_test.py @@ -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") diff --git a/compiler/tests/30_openram_front_end_test.py b/compiler/tests/30_openram_front_end_test.py index 1c190840..87b280dc 100755 --- a/compiler/tests/30_openram_front_end_test.py +++ b/compiler/tests/30_openram_front_end_test.py @@ -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") diff --git a/compiler/tests/50_riscv_func_test.py b/compiler/tests/50_riscv_func_test.py index 34f1fb98..5bd55e96 100755 --- a/compiler/tests/50_riscv_func_test.py +++ b/compiler/tests/50_riscv_func_test.py @@ -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) diff --git a/compiler/tests/golden/sram_2_16_1_freepdk45.v b/compiler/tests/golden/sram_2_16_1_freepdk45.v index 5edd2fee..46f89fa5 100644 --- a/compiler/tests/golden/sram_2_16_1_freepdk45.v +++ b/compiler/tests/golden/sram_2_16_1_freepdk45.v @@ -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 diff --git a/compiler/tests/golden/sram_2_16_1_scn4m_subm.v b/compiler/tests/golden/sram_2_16_1_scn4m_subm.v index cec47c19..cb95036b 100644 --- a/compiler/tests/golden/sram_2_16_1_scn4m_subm.v +++ b/compiler/tests/golden/sram_2_16_1_scn4m_subm.v @@ -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 diff --git a/compiler/tests/regress.py b/compiler/tests/regress.py index 878182a5..ea7e1991 100755 --- a/compiler/tests/regress.py +++ b/compiler/tests/regress.py @@ -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: diff --git a/compiler/tests/testutils.py b/compiler/tests/testutils.py index b6468749..928536a7 100644 --- a/compiler/tests/testutils.py +++ b/compiler/tests/testutils.py @@ -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 diff --git a/images/OpenRAM_logo_yellow_transparent.png b/images/OpenRAM_logo_yellow_transparent.png new file mode 100644 index 00000000..425d406a Binary files /dev/null and b/images/OpenRAM_logo_yellow_transparent.png differ diff --git a/images/OpenRAM_logo_yellow_transparent.svg b/images/OpenRAM_logo_yellow_transparent.svg new file mode 100644 index 00000000..5c276be7 --- /dev/null +++ b/images/OpenRAM_logo_yellow_transparent.svg @@ -0,0 +1,236 @@ + + + + + + + + image/svg+xml + + + + + + OpenRAM + + + + + + + + + + + + + + + + + + + + + + + + + + +