Add spice pruning for speed-up. Fix spice search bugs. Add time in stages to openram output.

This commit is contained in:
Matt Guthaus 2017-11-14 13:24:14 -08:00
parent e818cdc992
commit 29c5ab48f0
17 changed files with 280 additions and 66 deletions

View File

@ -17,7 +17,7 @@ class bitcell(design.design):
def __init__(self):
design.design.__init__(self, "cell_6t")
debug.info(2, "Create bitcell object")
debug.info(2, "Create bitcell")
self.width = bitcell.width
self.height = bitcell.height

View File

@ -22,7 +22,7 @@ class delay():
self.word_size = sram.word_size
self.addr_size = sram.addr_size
self.sram_sp_file = spfile
self.vdd = tech.spice["supply_voltage"]
self.gnd = tech.spice["gnd_voltage"]
@ -326,6 +326,7 @@ class delay():
self.probe_address = probe_address
self.probe_data = probe_data
def analyze(self,probe_address, probe_data, slews, loads):
"""main function to calculate the min period for a low_to_high
transistion and a high_to_low transistion returns a dictionary

View File

@ -10,6 +10,7 @@ import delay
import charutils as ch
import tech
import numpy as np
from trim_spice import trim_spice
OPTS = globals.get_opts()
@ -18,13 +19,25 @@ class lib:
def __init__(self, libname, sram, spfile, use_model=OPTS.analytical_delay):
self.sram = sram
self.spfile = spfile
self.sp_file = spfile
self.use_model = use_model
self.name = sram.name
self.num_words = sram.num_words
self.word_size = sram.word_size
self.addr_size = sram.addr_size
# Set up to trim the netlist here if that is enabled
if OPTS.trim_netlist:
self.sim_sp_file = "{}reduced.sp".format(OPTS.openram_temp)
self.trimsp=trim_spice(self.sp_file, self.sim_sp_file)
self.trimsp.set_configuration(self.sram.num_banks,
self.sram.num_rows,
self.sram.num_cols,
self.sram.word_size)
else:
# Else, use the non-reduced netlist file for simulation
self.sim_sp_file = self.sp_file
# These are the parameters to determine the table sizes
#self.load_scales = np.array([0.1, 0.25, 0.5, 1, 2, 4, 8])
self.load_scales = np.array([0.25, 1, 8])
@ -378,12 +391,15 @@ class lib:
try:
self.d
except AttributeError:
self.d = delay.delay(self.sram, self.spfile)
self.d = delay.delay(self.sram, self.sim_sp_file)
if self.use_model:
self.delay = self.d.analytical_model(self.sram,self.slews,self.loads)
else:
probe_address = "1" * self.addr_size
probe_data = self.word_size - 1
# We must trim based on a specific address and data bit
if OPTS.trim_netlist:
self.trimsp.trim(probe_address,probe_data)
self.delay = self.d.analyze(probe_address, probe_data, self.slews, self.loads)
def compute_setup_hold(self):

View File

@ -0,0 +1,117 @@
import debug
from math import log
class trim_spice():
"""
A utility to trim redundant parts of an SRAM spice netlist.
Input is an SRAM spice file. Output is an equivalent netlist
that works for a single address and range of data bits.
"""
def __init__(self, spfile, reduced_spfile):
self.sp_file = spfile
self.reduced_spfile = reduced_spfile
debug.info(1,"Trimming non-critical cells to speed-up characterization: {}.".format(reduced_spfile))
# Load the file into a buffer for performance
sp = open(self.sp_file, "r")
self.spice = sp.readlines()
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):
""" Set the configuration of SRAM sizes that we are simulating.
Need the: number of banks, number of rows in each bank, number of
columns in each bank, and data word size."""
self.num_banks = banks
self.num_rows = rows
self.num_columns = columns
self.word_size = word_size
self.words_per_row = self.num_columns / self.word_size
self.row_addr_size = int(log(self.num_rows, 2))
self.col_addr_size = int(log(self.words_per_row, 2))
self.bank_addr_size = self.col_addr_size + self.row_addr_size
self.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!)"""
# Always start fresh if we do multiple reductions
self.sp_buffer = self.spice
# Find the row and column indices for the removals
# Convert address froms tring to int
address = int(address,2)
array_row = address >> self.col_addr_size
# Which word in the array (0 if only one word)
if self.col_addr_size>0:
lower_mask = int(self.col_addr_size-1)
lower_address = address & lower_mask
else:
lower_address=0
# Which bit in the array
array_bit = lower_address*self.word_size + data_bit
# 1. Keep cells in the bitcell array based on WL and BL
wl_name = "wl[{}]".format(array_row)
bl_name = "bl[{}]".format(array_bit)
self.remove_insts("bitcell_array",[wl_name,bl_name])
# 2. Keep sense amps basd on BL
self.remove_insts("sense_amp_array",[bl_name])
# 3. Keep column muxes basd on BL
self.remove_insts("column_mux_array",[bl_name])
# 4. Keep write driver based on DATA
data_name = "data[{}]".format(data_bit)
self.remove_insts("write_driver_array",[data_name])
# 5. Keep wordline driver based on WL
# Need to keep the gater too
#self.remove_insts("wordline_driver",wl_name)
# 6. Keep precharges based on BL
self.remove_insts("precharge_array",[bl_name])
# Everything else isn't worth removing. :)
# Finally, write out the buffer as the new reduced file
sp = open(self.reduced_spfile, "w")
sp.write("\n".join(self.sp_buffer))
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
match of the line with a term so you can search for a single
net connection, the instance name, anything..
"""
start_name = ".SUBCKT {}".format(subckt_name)
end_name = ".ENDS {}".format(subckt_name)
in_subckt=False
new_buffer=[]
for line in self.sp_buffer:
if start_name in line:
new_buffer.append(line)
in_subckt=True
elif end_name in line:
new_buffer.append(line)
in_subckt=False
elif in_subckt:
for k in keep_inst_list:
if k in line:
new_buffer.append(line)
break
else:
new_buffer.append(line)
self.sp_buffer = new_buffer

View File

@ -34,6 +34,9 @@ def info(lev, str):
if (OPTS.debug_level >= lev):
frm = inspect.stack()[1]
mod = inspect.getmodule(frm[0])
print("[{0}]: {1}".format(frm[0].f_code.co_name,str))
# This sometimes gets a NoneType mod...
# print "[" , mod.__name__ , "]: ", str
#classname = frm.f_globals['__name__']
if mod.__name__ == None:
class_name=""
else:
class_name=mod.__name__
print("[{0}/{1}]: {2}".format(class_name,frm[0].f_code.co_name,str))

View File

@ -54,8 +54,8 @@ def parse_args():
help="Technology name"),
optparse.make_option("-s", "--spiceversion", dest="spice_version",
help="Spice simulator name"),
optparse.make_option("-r", "--reduce_netlist", action="store_true", dest="remove_noncritical",
help="Remove noncritical memory cells during characterization"),
optparse.make_option("-r", "--remove_netlist_trimming", action="store_false", dest="trim_netlist",
help="Disable removal of noncritical memory cells during characterization"),
optparse.make_option("-a", "--analytical", action="store_true", dest="analytical_delay",
help="Use analytical models to calculate delays (default)"),
optparse.make_option("-c", "--characterize", action="store_false", dest="analytical_delay",
@ -163,7 +163,7 @@ def set_calibre():
# everything.
if not OPTS.check_lvsdrc:
# over-ride the check LVS/DRC option
debug.info(0,"Over-riding LVS/DRC. Not performing inline LVS/DRC.")
debug.info(0,"Over-riding LVS/DRC. Not performing LVS/DRC.")
else:
# see if calibre is in the path (extend to other tools later)
for path in os.environ["PATH"].split(os.pathsep):
@ -175,7 +175,7 @@ def set_calibre():
break
else:
# otherwise, give warning and procede
debug.warning("Calibre not found. Not performing inline LVS/DRC.")
debug.warning("Calibre not found. Not performing LVS/DRC.")
OPTS.check_lvsdrc = False
def end_openram():
@ -234,13 +234,12 @@ def setup_paths():
def find_spice(spice_exe):
def find_spice(check_exe):
# Check if the preferred spice option exists in the path
for path in os.environ["PATH"].split(os.pathsep):
spice_exe = os.path.join(path, OPTS.spice_version)
spice_exe = os.path.join(path, check_exe)
# if it is found, then break and use first version
if is_exe(spice_exe):
debug.info(1, "Using spice: " + spice_exe)
OPTS.spice_exe = spice_exe
return True
return False
@ -253,21 +252,18 @@ def set_spice():
debug.info(1,"Using analytical delay models (no characterization)")
return
else:
debug.info(1,"Performing simulation-based characterization (may be slow!)")
OPTS.spice_exe = ""
spice_preferences = ["xa", "hspice", "ngspice", "ngspice.exe"]
if OPTS.spice_version != "":
if not find_spice(OPTS.spice_version):
debug.error("{0} not found. Unable to perform characterization.".format(OPTS.spice_version),1)
else:
for spice_exe in spice_preferences:
if find_spice(spice_exe):
break
else:
debug.warning("Unable to find spice {0}, trying another.".format(spice_exe))
spice_preferences = ["xa", "hspice", "ngspice", "ngspice.exe"]
if OPTS.spice_version != "":
if not find_spice(OPTS.spice_version):
debug.error("{0} not found. Unable to perform characterization.".format(OPTS.spice_version),1)
else:
for spice_name in spice_preferences:
if find_spice(spice_name):
OPTS.spice_version=spice_name
debug.info(1, "Using spice: " + OPTS.spice_exe)
break
else:
debug.info(1, "Could not find {}, trying next spice simulator. ".format(spice_name))
# set the input dir for spice files if using ngspice
if OPTS.spice_version == "ngspice":

View File

@ -36,7 +36,7 @@ class nand_2(design.design):
self.add_pins()
self.create_layout()
self.DRC_LVS()
#self.DRC_LVS()
def add_pins(self):
""" Add pins """

View File

@ -34,7 +34,7 @@ class nor_2(design.design):
self.add_pins()
self.create_layout()
self.DRC_LVS()
#self.DRC_LVS()
def add_pins(self):
self.add_pin_list(["A", "B", "Z", "vdd", "gnd"])

View File

@ -26,6 +26,14 @@ global OPTS
(OPTS, args) = globals.parse_args()
def print_time(name, now_time, last_time=None):
if last_time:
time = round((now_time-last_time).total_seconds(),1)
else:
time = now_time
print("** {0}: {1} seconds".format(name,time))
return now_time
# These depend on arguments, so don't load them until now.
import debug
@ -59,7 +67,7 @@ if (OPTS.output_name == ""):
num_banks,
OPTS.tech_name)
debug.info(1, "Output files are " + OPTS.output_name + ".(sp|gds|v|lib|lef)")
print("Output files are " + OPTS.output_name + ".(sp|gds|v|lib|lef)")
print("Technology: {0}".format(OPTS.tech_name))
print("Word size: {0}\nWords: {1}\nBanks: {2}".format(word_size,num_words,num_banks))
@ -68,20 +76,25 @@ print("Word size: {0}\nWords: {1}\nBanks: {2}".format(word_size,num_words,num_ba
if OPTS.analytical_delay:
print("Using analytical delay models (no characterization)")
else:
print("Performing simulation-based characterization (may be slow!)")
print("Performing simulation-based characterization with {}".format(OPTS.spice_version))
if OPTS.trim_netlist:
print("Trimming netlist to speed up characterization (sacrificing some accuracy).")
# only start importing modules after we have the config file
import calibre
import sram
print("Start: {0}".format(datetime.datetime.now()))
start_time = datetime.datetime.now()
last_time = start_time
print_time("Start",datetime.datetime.now())
# import SRAM test generation
s = sram.sram(word_size=word_size,
num_words=num_words,
num_banks=num_banks,
name=OPTS.output_name)
last_time=print_time("SRAM creation", datetime.datetime.now(), last_time)
# Measure design area
# Not working?
#cell_size = s.gds.measureSize(s.name)
@ -92,36 +105,40 @@ s = sram.sram(word_size=word_size,
spname = OPTS.output_path + s.name + ".sp"
print("SP: Writing to {0}".format(spname))
s.sp_write(spname)
last_time=print_time("Spice writing", datetime.datetime.now(), last_time)
gdsname = OPTS.output_path + s.name + ".gds"
print("GDS: Writing to {0}".format(gdsname))
s.gds_write(gdsname)
# Run Characterizer on the design
# Output the extracted design
sram_file = spname
if OPTS.use_pex:
sram_file = OPTS.output_path + "temp_pex.sp"
calibre.run_pex(s.name, gdsname, spname, output=sram_file)
# geenrate verilog
import verilog
vname = OPTS.output_path + s.name + ".v"
print("Verilog: Writing to {0}".format(vname))
verilog.verilog(vname,s)
# generate LEF
import lef
lefname = OPTS.output_path + s.name + ".lef"
print("LEF: Writing to {0}".format(lefname))
lef.lef(gdsname,lefname,s)
# generate lib
# Characterize the design
import lib
libname = OPTS.output_path + s.name + ".lib"
print("LIB: Writing to {0}".format(libname))
lib.lib(libname,s,sram_file)
last_time=print_time("Characterization", datetime.datetime.now(), last_time)
# Write the layout
gdsname = OPTS.output_path + s.name + ".gds"
print("GDS: Writing to {0}".format(gdsname))
s.gds_write(gdsname)
last_time=print_time("GDS", datetime.datetime.now(), last_time)
# Create a LEF physical model
import lef
lefname = OPTS.output_path + s.name + ".lef"
print("LEF: Writing to {0}".format(lefname))
lef.lef(gdsname,lefname,s)
last_time=print_time("LEF writing", datetime.datetime.now(), last_time)
# Write a verilog model
import verilog
vname = OPTS.output_path + s.name + ".v"
print("Verilog: Writing to {0}".format(vname))
verilog.verilog(vname,s)
last_time=print_time("Verilog writing", datetime.datetime.now(), last_time)
globals.end_openram()
print("End: {0}".format(datetime.datetime.now()))
print_time("End",datetime.datetime.now(), start_time)

View File

@ -18,8 +18,8 @@ class options(optparse.Values):
debug_level = 0
# This determines whether LVS and DRC is checked for each submodule.
check_lvsdrc = True
# Variable to select the variant of spice (hspice or ngspice right now)
spice_version = "hspice"
# Variable to select the variant of spice
spice_version = ""
# Should we print out the banner at startup
print_banner = True
# The Calibre executable being used which is derived from the user PATH.
@ -29,7 +29,7 @@ class options(optparse.Values):
# Run with extracted parasitics
use_pex = False
# Remove noncritical memory cells for characterization speed-up
remove_noncritical = False
trim_netlist = True
# Define the output file paths
output_path = ""
# Define the output file base name

View File

@ -32,7 +32,7 @@ class pinv(design.design):
self.add_pins()
self.create_layout()
self.DRC_LVS()
#self.DRC_LVS()
def add_pins(self):
"""Adds pins for spice netlist processing"""

View File

@ -17,7 +17,7 @@ class sense_amp(design.design):
def __init__(self, name):
design.design.__init__(self, name)
debug.info(2, "Create Sense Amp object")
debug.info(2, "Create sense_amp")
self.width = sense_amp.width
self.height = sense_amp.height

View File

@ -1,4 +1,3 @@
import math
import sys
from tech import drc, spice
import debug
@ -11,6 +10,7 @@ import getpass
from vector import vector
from globals import OPTS
class sram(design.design):
"""
Dynamically generated SRAM by connecting banks to control logic. The
@ -88,7 +88,7 @@ class sram(design.design):
# Compute the area of the bitcells and estimate a square bank (excluding auxiliary circuitry)
self.bank_area = self.bitcell.width*self.bitcell.height*self.num_bits_per_bank
self.bank_side_length = math.sqrt(self.bank_area)
self.bank_side_length = sqrt(self.bank_area)
# Estimate the words per row given the height of the bitcell and the square side length
self.tentative_num_cols = int(self.bank_side_length/self.bitcell.width)
@ -106,7 +106,7 @@ class sram(design.design):
self.row_addr_size = int(log(self.num_rows, 2))
self.col_addr_size = int(log(self.words_per_row, 2))
self.bank_addr_size = self.col_addr_size + self.row_addr_size
self.addr_size = self.bank_addr_size + int(math.log(self.num_banks, 2))
self.addr_size = self.bank_addr_size + int(log(self.num_banks, 2))
def estimate_words_per_row(self,tentative_num_cols, word_size):

View File

@ -0,0 +1,62 @@
#!/usr/bin/env python2.7
"""
Check the .lib file for an SRAM with pruning
"""
import unittest
from testutils import header,isapproxdiff
import sys,os
sys.path.append(os.path.join(sys.path[0],".."))
import globals
import debug
import calibre
OPTS = globals.get_opts()
class lib_test(unittest.TestCase):
def runTest(self):
OPTS.analytical_delay = False
globals.init_openram("config_20_{0}".format(OPTS.tech_name))
# we will manually run lvs/drc
OPTS.check_lvsdrc = False
import sram
import lib
debug.info(1, "Testing timing for sample 2 bit, 16 words SRAM with 1 bank")
s = sram.sram(word_size=2,
num_words=OPTS.config.num_words,
num_banks=OPTS.config.num_banks,
name="sram_2_16_1_{0}".format(OPTS.tech_name))
OPTS.check_lvsdrc = True
tempspice = OPTS.openram_temp + "temp.sp"
s.sp_write(tempspice)
filename = s.name + ".lib"
libname = OPTS.openram_temp + filename
lib.lib(libname=libname,sram=s,spfile=tempspice,use_model=False)
# let's diff the result with a golden model
golden = "{0}/golden/{1}".format(os.path.dirname(os.path.realpath(__file__)),filename)
# From an experiment, a 15% difference between between pruned and not was found.
self.assertEqual(isapproxdiff(libname,golden,0.15),True)
os.system("rm {0}".format(libname))
OPTS.analytical_delay = True
globals.end_openram()
# instantiate a copdsay of the class to actually run the test
if __name__ == "__main__":
(OPTS, args) = globals.parse_args()
del sys.argv[1:]
header(__file__, OPTS.tech_name)
unittest.main()

View File

@ -18,6 +18,7 @@ class lib_test(unittest.TestCase):
def runTest(self):
OPTS.analytical_delay = False
OPTS.trim_netlist = False
globals.init_openram("config_20_{0}".format(OPTS.tech_name))
# we will manually run lvs/drc
OPTS.check_lvsdrc = False
@ -46,6 +47,7 @@ class lib_test(unittest.TestCase):
os.system("rm {0}".format(libname))
OPTS.analytical_delay = True
OPTS.trim_netlist = True
globals.end_openram()
# instantiate a copdsay of the class to actually run the test

View File

@ -21,7 +21,7 @@ class tri_gate(design.design):
name = "tri{0}".format(tri_gate.unique_id)
tri_gate.unique_id += 1
design.design.__init__(self, name)
debug.info(2, "Create tri_gate object")
debug.info(2, "Create tri_gate")
self.width = tri_gate.width
self.height = tri_gate.height

View File

@ -17,7 +17,7 @@ class write_driver(design.design):
def __init__(self, name):
design.design.__init__(self, name)
debug.info(2, "Create write_driver object")
debug.info(2, "Create write_driver")
self.width = write_driver.width
self.height = write_driver.height