OpenRAM/compiler/base/hierarchy_spice.py

502 lines
21 KiB
Python
Raw Normal View History

# See LICENSE for licensing information.
#
2019-06-14 17:43:41 +02:00
# Copyright (c) 2016-2019 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.
#
2016-11-08 18:57:35 +01:00
import debug
import re
import os
import math
import tech
2020-06-22 20:33:02 +02:00
from delay_data import delay_data
from wire_spice_model import wire_spice_model
from power_data import power_data
import logical_effort
2020-04-03 20:37:06 +02:00
class spice():
2016-11-08 18:57:35 +01:00
"""
This provides a set of useful generic types for hierarchy
management. If a module is a custom designed cell, it will read from
the GDS and spice files and perform LVS/DRC. If it is dynamically
generated, it should implement a constructor to create the
layout/netlist and perform LVS/DRC.
Class consisting of a set of modules and instances of these modules
"""
def __init__(self, name):
self.name = name
2019-07-24 17:15:10 +02:00
self.valid_signal_types = ["INOUT", "INPUT", "OUTPUT", "POWER", "GROUND"]
2019-01-17 01:15:38 +01:00
# Holds subckts/mods for this module
2020-04-03 20:37:06 +02:00
self.mods = []
2019-01-17 01:15:38 +01:00
# Holds the pins for this module
self.pins = []
2019-01-17 01:15:38 +01:00
# The type map of each pin: INPUT, OUTPUT, INOUT, POWER, GROUND
2016-11-08 18:57:35 +01:00
# for each instance, this is the set of nets/nodes that map to the pins for this instance
2020-04-03 20:37:06 +02:00
self.pin_type = {}
2019-01-17 01:15:38 +01:00
# THE CONNECTIONS MUST MATCH THE ORDER OF THE PINS (restriction imposed by the
2016-11-08 18:57:35 +01:00
# Spice format)
self.conns = []
# If this is set, it will out output subckt or isntances of this (for row/col caps etc.)
self.no_instances = False
# Keep track of any comments to add the the spice
try:
self.commments
except AttributeError:
self.comments = []
2016-11-08 18:57:35 +01:00
self.sp_read()
############################################################
# Spice circuit
############################################################
def add_comment(self, comment):
""" Add a comment to the spice file """
try:
self.commments
except AttributeError:
self.comments = []
self.comments.append(comment)
def add_pin(self, name, pin_type="INOUT"):
""" Adds a pin to the pins list. Default type is INOUT signal. """
2016-11-08 18:57:35 +01:00
self.pins.append(name)
self.pin_type[name]=pin_type
2020-04-03 20:37:06 +02:00
debug.check(pin_type in self.valid_signal_types,
"Invalid signaltype for {0}: {1}".format(name,
pin_type))
2019-07-24 17:15:10 +02:00
def add_pin_list(self, pin_list, pin_type="INOUT"):
""" Adds a pin_list to the pins list """
# The type list can be a single type for all pins
# or a list that is the same length as the pin list.
2019-07-24 17:15:10 +02:00
if type(pin_type)==str:
for pin in pin_list:
2020-04-03 20:37:06 +02:00
debug.check(pin_type in self.valid_signal_types,
"Invalid signaltype for {0}: {1}".format(pin,
pin_type))
self.add_pin(pin, pin_type)
2019-07-24 17:15:10 +02:00
elif len(pin_type)==len(pin_list):
2020-04-03 20:37:06 +02:00
for (pin, ptype) in zip(pin_list, pin_type):
debug.check(ptype in self.valid_signal_types,
"Invalid signaltype for {0}: {1}".format(pin,
ptype))
self.add_pin(pin, ptype)
else:
debug.error("Mismatch in type and pin list lengths.", -1)
def add_pin_types(self, type_list):
2020-04-03 20:37:06 +02:00
"""
Add pin types for all the cell's pins.
Typically, should only be used for handmade cells.
"""
# This only works if self.pins == bitcell.pin_names
if self.pin_names != self.pins:
debug.error("{} spice subcircuit port names do not match pin_names\
\n SPICE names={}\
\n Module names={}\
2020-04-03 20:37:06 +02:00
".format(self.name, self.pin_names, self.pins), 1)
self.pin_type = {pin: type for pin, type in zip(self.pin_names, type_list)}
def get_pin_type(self, name):
""" Returns the type of the signal pin. """
2019-07-24 17:15:10 +02:00
pin_type = self.pin_type[name]
2020-04-03 20:37:06 +02:00
debug.check(pin_type in self.valid_signal_types,
"Invalid signaltype for {0}: {1}".format(name, pin_type))
2019-07-24 17:15:10 +02:00
return pin_type
2016-11-08 18:57:35 +01:00
def get_pin_dir(self, name):
""" Returns the direction of the pin. (Supply/ground are INOUT). """
2020-04-03 20:37:06 +02:00
if self.pin_type[name] in ["POWER", "GROUND"]:
return "INOUT"
else:
return self.pin_type[name]
def get_inputs(self):
""" These use pin types to determine pin lists. These
may be over-ridden by submodules that didn't use pin directions yet."""
input_list = []
for pin in self.pins:
if self.pin_type[pin]=="INPUT":
input_list.append(pin)
return input_list
def get_outputs(self):
""" These use pin types to determine pin lists. These
may be over-ridden by submodules that didn't use pin directions yet."""
output_list = []
for pin in self.pins:
if self.pin_type[pin]=="OUTPUT":
output_list.append(pin)
return output_list
2019-06-20 01:03:21 +02:00
def copy_pins(self, other_module, suffix=""):
""" This will copy all of the pins from the other module and add an optional suffix."""
for pin in other_module.pins:
2020-04-03 20:37:06 +02:00
self.add_pin(pin + suffix, other_module.get_pin_type(pin))
2016-11-08 18:57:35 +01:00
def get_inouts(self):
""" These use pin types to determine pin lists. These
may be over-ridden by submodules that didn't use pin directions yet."""
inout_list = []
for pin in self.pins:
if self.pin_type[pin]=="INOUT":
inout_list.append(pin)
return inout_list
2016-11-08 18:57:35 +01:00
def add_mod(self, mod):
"""Adds a subckt/submodule to the subckt hierarchy"""
self.mods.append(mod)
def connect_inst(self, args, check=True):
"""Connects the pins of the last instance added
It is preferred to use the function with the check to find if
there is a problem. The check option can be set to false
2016-11-08 18:57:35 +01:00
where we dynamically generate groups of connections after a
group of modules are generated."""
num_pins = len(self.insts[-1].mod.pins)
num_args = len(args)
if (check and num_pins != num_args):
2018-10-15 22:23:31 +02:00
from pprint import pformat
if num_pins < num_args:
mod_pins = self.insts[-1].mod.pins + [""] * (num_args - num_pins)
arg_pins = args
else:
arg_pins = args + [""] * (num_pins - num_args)
mod_pins = self.insts[-1].mod.pins
modpins_string = "\n".join(["{0} -> {1}".format(arg, mod) for (arg, mod) in zip(arg_pins, mod_pins)])
debug.error("Connection mismatch:\nInst ({0}) -> Mod ({1})\n{2}".format(num_args,
num_pins,
modpins_string),
1)
2016-11-08 18:57:35 +01:00
self.conns.append(args)
if check and (len(self.insts)!=len(self.conns)):
2018-10-15 22:23:31 +02:00
from pprint import pformat
insts_string=pformat(self.insts)
conns_string=pformat(self.conns)
2016-11-08 18:57:35 +01:00
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.name,
len(self.insts),
len(self.conns)))
2020-04-03 20:37:06 +02:00
debug.error("Instances: \n" + str(insts_string))
2016-11-08 18:57:35 +01:00
debug.error("-----")
2020-04-03 20:37:06 +02:00
debug.error("Connections: \n" + str(conns_string), 1)
2016-11-08 18:57:35 +01:00
def get_conns(self, inst):
"""Returns the connections of a given instance."""
for i in range(len(self.insts)):
if inst is self.insts[i]:
return self.conns[i]
2020-04-03 20:37:06 +02:00
# If not found, returns None
return None
2016-11-08 18:57:35 +01:00
def sp_read(self):
2020-04-03 20:37:06 +02:00
"""
Reads the sp file (and parse the pins) from the library
Otherwise, initialize it to null for dynamic generation
"""
if self.sp_file and os.path.isfile(self.sp_file):
debug.info(3, "opening {0}".format(self.sp_file))
2016-11-08 18:57:35 +01:00
f = open(self.sp_file)
self.spice = f.readlines()
for i in range(len(self.spice)):
self.spice[i] = self.spice[i].rstrip(" \n")
f.close()
2016-11-08 18:57:35 +01:00
# find the correct subckt line in the file
subckt = re.compile("^.subckt {}".format(self.name), re.IGNORECASE)
subckt_line = list(filter(subckt.search, self.spice))[0]
2016-11-08 18:57:35 +01:00
# parses line into ports and remove subckt
self.pins = subckt_line.split(" ")[2:]
else:
debug.info(4, "no spfile {0}".format(self.sp_file))
2016-11-08 18:57:35 +01:00
self.spice = []
# We don't define self.lvs and will use self.spice if dynamically created
# or they are the same file
2020-06-24 00:39:26 +02:00
if self.lvs_file != self.sp_file and os.path.isfile(self.lvs_file):
debug.info(3, "opening {0}".format(self.lvs_file))
f = open(self.lvs_file)
self.lvs = f.readlines()
for i in range(len(self.lvs)):
self.lvs[i] = self.lvs[i].rstrip(" \n")
f.close()
# pins and subckt should be the same
# find the correct subckt line in the file
subckt = re.compile("^.subckt {}".format(self.name), re.IGNORECASE)
subckt_line = list(filter(subckt.search, self.lvs))[0]
# parses line into ports and remove subckt
lvs_pins = subckt_line.split(" ")[2:]
debug.check(lvs_pins == self.pins, "LVS and spice file pin mismatch.")
2019-05-21 03:35:52 +02:00
def check_net_in_spice(self, net_name):
"""Checks if a net name exists in the current. Intended to be check nets in hand-made cells."""
2020-04-03 20:37:06 +02:00
# Remove spaces and lower case then add spaces.
# Nets are separated by spaces.
net_formatted = ' ' + net_name.lstrip().rstrip().lower() + ' '
2019-05-21 03:35:52 +02:00
for line in self.spice:
2020-04-03 20:37:06 +02:00
# Lowercase the line and remove any part of the line that is a comment.
2019-05-21 03:35:52 +02:00
line = line.lower().split('*')[0]
2020-04-03 20:37:06 +02:00
# Skip .subckt or .ENDS lines
2019-05-21 03:35:52 +02:00
if line.find('.') == 0:
continue
if net_formatted in line:
return True
return False
def do_nets_exist(self, nets):
"""For handmade cell, checks sp file contains the storage nodes."""
nets_match = True
for net in nets:
nets_match = nets_match and self.check_net_in_spice(net)
2020-04-03 20:37:06 +02:00
return nets_match
2019-05-21 03:35:52 +02:00
2016-11-08 18:57:35 +01:00
def contains(self, mod, modlist):
for x in modlist:
if x.name == mod.name:
return True
return False
def sp_write_file(self, sp, usedMODS, lvs_netlist=False):
"""
Recursive spice subcircuit write;
Writes the spice subcircuit from the library or the dynamically generated one
"""
2020-06-22 20:33:02 +02:00
if self.no_instances:
return
elif not self.spice:
2020-06-22 20:33:02 +02:00
# If spice isn't defined, we dynamically generate one.
2016-11-08 18:57:35 +01:00
# recursively write the modules
for i in self.mods:
if self.contains(i, usedMODS):
continue
usedMODS.append(i)
i.sp_write_file(sp, usedMODS, lvs_netlist)
2016-11-08 18:57:35 +01:00
if len(self.insts) == 0:
return
if self.pins == []:
return
2016-11-08 18:57:35 +01:00
# write out the first spice line (the subcircuit)
sp.write("\n.SUBCKT {0} {1}\n".format(self.name,
" ".join(self.pins)))
for pin in self.pins:
2020-04-03 20:37:06 +02:00
sp.write("* {1:6}: {0} \n".format(pin, self.pin_type[pin]))
for line in self.comments:
sp.write("* {}\n".format(line))
2016-11-08 18:57:35 +01:00
# every instance must have a set of connections, even if it is empty.
2020-04-03 20:37:06 +02:00
if len(self.insts) != len(self.conns):
2016-11-08 18:57:35 +01:00
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.name,
len(self.insts),
len(self.conns)))
2020-04-03 20:37:06 +02:00
debug.error("Instances: \n" + str(self.insts))
2016-11-08 18:57:35 +01:00
debug.error("-----")
2020-04-03 20:37:06 +02:00
debug.error("Connections: \n" + str(self.conns), 1)
2016-11-08 18:57:35 +01:00
for i in range(len(self.insts)):
# we don't need to output connections of empty instances.
# 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
2020-04-03 23:06:56 +02:00
if lvs_netlist 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")
elif hasattr(self.insts[i].mod, "spice_device"):
sp.write(self.insts[i].mod.spice_device.format(self.insts[i].name,
" ".join(self.conns[i])))
sp.write("\n")
else:
sp.write("X{0} {1} {2}\n".format(self.insts[i].name,
" ".join(self.conns[i]),
self.insts[i].mod.name))
2016-11-08 18:57:35 +01:00
sp.write(".ENDS {0}\n".format(self.name))
else:
2020-06-22 20:33:02 +02:00
# If spice is a hard module, output the spice file contents.
2016-11-08 18:57:35 +01:00
# Including the file path makes the unit test fail for other users.
2020-04-03 20:37:06 +02:00
# if os.path.isfile(self.sp_file):
2016-11-08 18:57:35 +01:00
# sp.write("\n* {0}\n".format(self.sp_file))
if lvs_netlist and hasattr(self, "lvs"):
sp.write("\n".join(self.lvs))
else:
sp.write("\n".join(self.spice))
2016-11-08 18:57:35 +01:00
sp.write("\n")
def sp_write(self, spname):
"""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)
del usedMODS
spfile.close()
def analytical_delay(self, corner, slew, load=0.0):
"""Inform users undefined delay module while building new modules"""
2020-04-03 20:37:06 +02:00
# FIXME: Slew is not used in the model right now.
# Can be added heuristically as linear factor
relative_cap = logical_effort.convert_farad_to_relative_c(load)
stage_effort = self.get_stage_effort(relative_cap)
# If it fails, then keep running with a valid object.
2020-06-22 20:33:02 +02:00
if not stage_effort:
return delay_data(0.0, 0.0)
abs_delay = stage_effort.get_absolute_delay()
corner_delay = self.apply_corners_analytically(abs_delay, corner)
SLEW_APPROXIMATION = 0.1
2020-04-03 20:37:06 +02:00
corner_slew = SLEW_APPROXIMATION * corner_delay
return delay_data(corner_delay, corner_slew)
def get_stage_effort(self, cout, inp_is_rise=True):
"""Inform users undefined delay module while building new modules"""
debug.warning("Design Class {0} logical effort function needs to be defined"
.format(self.__class__.__name__))
debug.warning("Class {0} name {1}"
.format(self.__class__.__name__,
self.name))
2020-04-03 20:37:06 +02:00
return None
def get_cin(self):
"""Returns input load in Femto-Farads. All values generated using
relative capacitance function then converted based on tech file parameter."""
# Override this function within a module if a more accurate input capacitance is needed.
# Input/outputs with differing capacitances is not implemented.
relative_cap = self.input_load()
return logical_effort.convert_relative_c_to_farad(relative_cap)
def input_load(self):
"""Inform users undefined relative capacitance functions used for analytical delays."""
debug.warning("Design Class {0} input capacitance function needs to be defined"
.format(self.__class__.__name__))
debug.warning("Class {0} name {1}"
2020-04-03 20:37:06 +02:00
.format(self.__class__.__name__,
self.name))
return 0
2020-04-03 20:37:06 +02:00
def cal_delay_with_rc(self, corner, r, c, slew, swing=0.5):
"""
Calculate the delay of a mosfet by
modeling it as a resistance driving a capacitance
"""
2020-04-03 20:37:06 +02:00
swing_factor = abs(math.log(1 - swing)) # time constant based on swing
delay = swing_factor * r * c # c is in ff and delay is in fs
delay = self.apply_corners_analytically(delay, corner)
2020-04-03 20:37:06 +02:00
delay = delay * 0.001 # make the unit to ps
2020-04-03 20:37:06 +02:00
# Output slew should be linear to input slew which is described
2017-07-06 17:42:25 +02:00
# as 0.005* slew.
2017-07-06 17:42:25 +02:00
# The slew will be also influenced by the delay.
2020-04-03 20:37:06 +02:00
# If no input slew(or too small to make impact)
# The mimum slew should be the time to charge RC.
# Delay * 2 is from 0 to 100% swing. 0.6*2*delay is from 20%-80%.
2017-07-06 17:42:25 +02:00
slew = delay * 0.6 * 2 + 0.005 * slew
2020-04-03 20:37:06 +02:00
return delay_data(delay=delay, slew=slew)
def apply_corners_analytically(self, delay, corner):
"""Multiply delay by corner factors"""
2020-04-03 20:37:06 +02:00
proc, vdd, temp = corner
# FIXME: type of delay is needed to know which process to use.
proc_mult = max(self.get_process_delay_factor(proc))
volt_mult = self.get_voltage_delay_factor(vdd)
temp_mult = self.get_temp_delay_factor(temp)
return delay * proc_mult * volt_mult * temp_mult
def get_process_delay_factor(self, proc):
"""Returns delay increase estimate based off process
Currently does +/-10 for fast/slow corners."""
proc_factors = []
for mos_proc in proc:
if mos_proc == 'T':
proc_factors.append(1.0)
elif mos_proc == 'F':
proc_factors.append(0.9)
elif mos_proc == 'S':
2020-04-03 20:37:06 +02:00
proc_factors.append(1.1)
return proc_factors
def get_voltage_delay_factor(self, voltage):
"""Returns delay increase due to voltage.
Implemented as linear factor based off nominal voltage.
"""
2020-04-03 20:37:06 +02:00
return tech.spice["nom_supply_voltage"] / voltage
def get_temp_delay_factor(self, temp):
"""Returns delay increase due to temperature (in C).
Determines effect on threshold voltage and then linear factor is estimated.
"""
2020-04-03 20:37:06 +02:00
# Some portions of equation condensed (phi_t = k*T/q for T in Kelvin) in mV
# (k/q)/100 = .008625, The division 100 simplifies the conversion from C to K and mV to V
thermal_voltage_nom = 0.008625 * tech.spice["nom_temperature"]
thermal_voltage = 0.008625 * temp
vthresh = (tech.spice["nom_threshold"] + 2 * (thermal_voltage - thermal_voltage_nom))
# Calculate effect on Vdd-Vth.
# The current vdd is not used here.
# A separate vdd factor is calculated.
return (tech.spice["nom_supply_voltage"] - tech.spice["nom_threshold"]) / (tech.spice["nom_supply_voltage"] - vthresh)
2017-07-06 17:42:25 +02:00
def return_delay(self, delay, slew):
return delay_data(delay, slew)
2020-04-03 20:37:06 +02:00
def generate_rc_net(self, lump_num, wire_length, wire_width):
return wire_spice_model(lump_num, wire_length, wire_width)
def calc_dynamic_power(self, corner, c, freq, swing=1.0):
2020-04-03 20:37:06 +02:00
"""
Calculate dynamic power using effective capacitance, frequency, and corner (PVT)
"""
2020-04-03 20:37:06 +02:00
proc, vdd, temp = corner
net_vswing = vdd * swing
power_dyn = c * vdd * net_vswing * freq
2020-04-03 20:37:06 +02:00
# A pply process and temperature factors.
# Roughly, process and Vdd affect the delay which affects the power.
# No other estimations are currently used. Increased delay->slower freq.->less power
proc_div = max(self.get_process_delay_factor(proc))
temp_div = self.get_temp_delay_factor(temp)
2020-04-03 20:37:06 +02:00
power_dyn = power_dyn / (proc_div * temp_div)
2020-04-03 20:37:06 +02:00
return power_dyn
def return_power(self, dynamic=0.0, leakage=0.0):
return power_data(dynamic, leakage)