2019-04-26 21:21:50 +02:00
|
|
|
# See LICENSE for licensing information.
|
|
|
|
|
#
|
2021-01-22 20:23:28 +01:00
|
|
|
# Copyright (c) 2016-2021 Regents of the University of California and The Board
|
2019-06-14 17:43:41 +02:00
|
|
|
# of Regents for the Oklahoma Agricultural and Mechanical College
|
|
|
|
|
# (acting for and on behalf of Oklahoma State University)
|
|
|
|
|
# All rights reserved.
|
2019-04-26 21:21:50 +02:00
|
|
|
#
|
2016-11-08 18:57:35 +01:00
|
|
|
import debug
|
|
|
|
|
import re
|
|
|
|
|
import os
|
2017-05-30 21:50:07 +02:00
|
|
|
import math
|
2019-03-04 09:42:18 +01:00
|
|
|
import tech
|
2020-11-19 01:27:28 +01:00
|
|
|
from globals import OPTS
|
2020-11-03 01:00:16 +01:00
|
|
|
from pprint import pformat
|
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
|
2019-08-07 10:50:48 +02:00
|
|
|
import logical_effort
|
2019-07-04 19:34:14 +02:00
|
|
|
|
2021-12-17 19:21:34 +01:00
|
|
|
|
2019-01-11 23:15:16 +01: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
|
|
|
|
|
"""
|
|
|
|
|
|
2020-11-03 01:00:16 +01:00
|
|
|
def __init__(self, name, cell_name):
|
2020-11-16 19:14:37 +01:00
|
|
|
# This gets set in both spice and layout so either can be called first.
|
2016-11-08 18:57:35 +01:00
|
|
|
self.name = name
|
2020-11-03 01:00:16 +01:00
|
|
|
self.cell_name = cell_name
|
2020-11-19 01:27:28 +01:00
|
|
|
self.sp_file = OPTS.openram_tech + "sp_lib/" + cell_name + ".sp"
|
|
|
|
|
|
|
|
|
|
# If we have a separate lvs directory, then all the lvs files
|
|
|
|
|
# should be in there (all or nothing!)
|
|
|
|
|
try:
|
2021-12-17 19:21:34 +01:00
|
|
|
from tech import lvs_name
|
|
|
|
|
lvs_dir = OPTS.openram_tech + lvs_name + "_lvs_lib/"
|
|
|
|
|
except ImportError:
|
|
|
|
|
lvs_dir = OPTS.openram_tech + "lvs_lib/"
|
|
|
|
|
if not os.path.exists(lvs_dir):
|
|
|
|
|
lvs_dir = OPTS.openram_tech + "lvs_lib/"
|
|
|
|
|
|
|
|
|
|
self.lvs_file = lvs_dir + cell_name + ".sp"
|
|
|
|
|
if not os.path.exists(self.lvs_file):
|
2020-11-19 01:27:28 +01:00
|
|
|
self.lvs_file = self.sp_file
|
2021-11-22 19:51:40 +01:00
|
|
|
|
2020-11-22 17:24:47 +01:00
|
|
|
self.valid_signal_types = ["INOUT", "INPUT", "OUTPUT", "BIAS", "POWER", "GROUND"]
|
2019-01-17 01:15:38 +01:00
|
|
|
# Holds subckts/mods for this module
|
2021-11-22 19:51:40 +01:00
|
|
|
self.mods = set()
|
2020-11-17 20:12:59 +01:00
|
|
|
# Holds the pins for this module (in order)
|
2019-01-24 02:27:15 +01:00
|
|
|
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 = {}
|
2020-11-17 20:12:59 +01:00
|
|
|
# An (optional) list of indices to reorder the pins to match the spice.
|
|
|
|
|
self.pin_indices = []
|
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 = []
|
2020-06-22 21:35:37 +02:00
|
|
|
# If this is set, it will out output subckt or isntances of this (for row/col caps etc.)
|
|
|
|
|
self.no_instances = False
|
2021-04-07 02:01:52 +02:00
|
|
|
# If we are doing a trimmed netlist, these are the instance that will be filtered
|
|
|
|
|
self.trim_insts = set()
|
2019-01-24 02:27:15 +01:00
|
|
|
# Keep track of any comments to add the the spice
|
2019-04-26 20:57:29 +02:00
|
|
|
try:
|
|
|
|
|
self.commments
|
2020-04-03 22:39:54 +02:00
|
|
|
except AttributeError:
|
2019-04-26 20:57:29 +02:00
|
|
|
self.comments = []
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
self.sp_read()
|
|
|
|
|
|
|
|
|
|
############################################################
|
|
|
|
|
# Spice circuit
|
|
|
|
|
############################################################
|
|
|
|
|
|
2019-01-24 02:27:15 +01:00
|
|
|
def add_comment(self, comment):
|
|
|
|
|
""" Add a comment to the spice file """
|
2019-07-17 02:30:31 +02:00
|
|
|
|
2019-04-26 20:57:29 +02:00
|
|
|
try:
|
|
|
|
|
self.commments
|
2020-04-03 22:39:54 +02:00
|
|
|
except AttributeError:
|
2019-04-26 20:57:29 +02:00
|
|
|
self.comments = []
|
2019-07-17 02:30:31 +02:00
|
|
|
|
|
|
|
|
self.comments.append(comment)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2017-12-19 18:01:24 +01:00
|
|
|
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)
|
2017-12-19 18:01:24 +01:00
|
|
|
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))
|
2017-12-19 18:01:24 +01:00
|
|
|
|
2019-07-24 17:15:10 +02:00
|
|
|
def add_pin_list(self, pin_list, pin_type="INOUT"):
|
2017-12-19 18:01:24 +01:00
|
|
|
""" 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:
|
2017-12-19 18:01:24 +01:00
|
|
|
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)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
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)
|
2017-12-19 18:01:24 +01:00
|
|
|
else:
|
|
|
|
|
debug.error("Mismatch in type and pin list lengths.", -1)
|
|
|
|
|
|
2020-11-17 20:12:59 +01:00
|
|
|
def add_pin_indices(self, index_list):
|
|
|
|
|
"""
|
|
|
|
|
Add pin indices for all the cell's pins.
|
|
|
|
|
"""
|
|
|
|
|
self.pin_indices = index_list
|
|
|
|
|
|
|
|
|
|
def get_ordered_inputs(self, input_list):
|
|
|
|
|
"""
|
|
|
|
|
Return the inputs reordered to match the pins.
|
|
|
|
|
"""
|
|
|
|
|
if not self.pin_indices:
|
|
|
|
|
return input_list
|
|
|
|
|
|
|
|
|
|
new_list = [input_list[x] for x in self.pin_indices]
|
|
|
|
|
return new_list
|
2021-11-22 19:51:40 +01:00
|
|
|
|
2019-05-07 09:52:27 +02:00
|
|
|
def add_pin_types(self, type_list):
|
2020-04-03 20:37:06 +02:00
|
|
|
"""
|
|
|
|
|
Add pin types for all the cell's pins.
|
|
|
|
|
"""
|
|
|
|
|
# This only works if self.pins == bitcell.pin_names
|
2020-11-14 00:55:55 +01:00
|
|
|
if len(type_list) != len(self.pins):
|
|
|
|
|
debug.error("{} spice subcircuit number of port types does not match number of pins\
|
2019-05-07 09:52:27 +02:00
|
|
|
\n SPICE names={}\
|
|
|
|
|
\n Module names={}\
|
2020-11-14 00:55:55 +01:00
|
|
|
".format(self.name, self.pins, type_list), 1)
|
|
|
|
|
self.pin_type = {pin: type for pin, type in zip(self.pins, type_list)}
|
2021-11-22 19:51:40 +01:00
|
|
|
|
2017-12-19 18:01:24 +01:00
|
|
|
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
|
|
|
|
2017-12-19 18:01:24 +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"]:
|
2017-12-19 18:01:24 +01:00
|
|
|
return "INOUT"
|
|
|
|
|
else:
|
|
|
|
|
return self.pin_type[name]
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-03-15 01:30:41 +01:00
|
|
|
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
|
|
|
|
2019-05-07 09:52:27 +02: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 connect_inst(self, args, check=True):
|
2020-11-17 20:12:59 +01:00
|
|
|
"""
|
|
|
|
|
Connects the pins of the last instance added
|
2017-08-24 00:02:15 +02:00
|
|
|
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
|
2020-11-17 20:12:59 +01:00
|
|
|
group of modules are generated.
|
|
|
|
|
"""
|
2021-11-22 19:51:40 +01:00
|
|
|
|
2020-08-27 23:03:05 +02:00
|
|
|
num_pins = len(self.insts[-1].mod.pins)
|
|
|
|
|
num_args = len(args)
|
2020-11-17 20:12:59 +01:00
|
|
|
|
|
|
|
|
# Order the arguments if the hard cell has a custom port order
|
|
|
|
|
ordered_args = self.get_ordered_inputs(args)
|
2021-11-22 19:51:40 +01:00
|
|
|
|
2020-08-27 23:03:05 +02:00
|
|
|
if (check and num_pins != num_args):
|
|
|
|
|
if num_pins < num_args:
|
|
|
|
|
mod_pins = self.insts[-1].mod.pins + [""] * (num_args - num_pins)
|
2020-11-17 20:12:59 +01:00
|
|
|
arg_pins = ordered_args
|
2020-08-27 23:03:05 +02:00
|
|
|
else:
|
2020-11-17 20:12:59 +01:00
|
|
|
arg_pins = ordered_args + [""] * (num_pins - num_args)
|
2020-08-27 23:03:05 +02:00
|
|
|
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)
|
|
|
|
|
|
2020-11-17 20:12:59 +01:00
|
|
|
self.conns.append(ordered_args)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2020-11-17 20:12:59 +01:00
|
|
|
# This checks if we don't have enough instance port connections for the number of insts
|
2016-11-08 18:57:35 +01:00
|
|
|
if check and (len(self.insts)!=len(self.conns)):
|
2018-10-15 22:23:31 +02:00
|
|
|
insts_string=pformat(self.insts)
|
|
|
|
|
conns_string=pformat(self.conns)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
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
|
|
|
|
2019-05-14 23:44:49 +02: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
|
2019-05-14 23:44:49 +02:00
|
|
|
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
|
|
|
|
|
"""
|
2018-08-22 20:37:24 +02:00
|
|
|
if self.sp_file and os.path.isfile(self.sp_file):
|
2016-11-15 17:57:06 +01:00
|
|
|
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")
|
2018-05-12 01:32:00 +02:00
|
|
|
f.close()
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2018-02-27 17:59:46 +01:00
|
|
|
# find the correct subckt line in the file
|
2020-11-03 01:00:16 +01:00
|
|
|
subckt = re.compile("^.subckt {}".format(self.cell_name), re.IGNORECASE)
|
2018-05-12 01:32:00 +02:00
|
|
|
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:
|
2020-05-14 20:20:37 +02:00
|
|
|
debug.info(4, "no spfile {0}".format(self.sp_file))
|
2016-11-08 18:57:35 +01:00
|
|
|
self.spice = []
|
|
|
|
|
|
2020-04-03 22:39:54 +02:00
|
|
|
# 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):
|
2020-04-03 22:39:54 +02:00
|
|
|
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
|
2020-11-03 01:00:16 +01:00
|
|
|
subckt = re.compile("^.subckt {}".format(self.cell_name), re.IGNORECASE)
|
2020-04-03 22:39:54 +02:00
|
|
|
subckt_line = list(filter(subckt.search, self.lvs))[0]
|
|
|
|
|
# parses line into ports and remove subckt
|
|
|
|
|
lvs_pins = subckt_line.split(" ")[2:]
|
2020-11-16 22:42:42 +01:00
|
|
|
debug.check(lvs_pins == self.pins,
|
2021-12-17 19:21:34 +01:00
|
|
|
"Spice netlists for LVS and simulation have port mismatches:\n{0} (LVS {1})\nvs\n{2} (sim {3})".format(lvs_pins,
|
|
|
|
|
self.lvs_file,
|
|
|
|
|
self.pins,
|
|
|
|
|
self.sp_file))
|
2020-11-03 15:29:17 +01:00
|
|
|
|
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
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-05-21 07:50:03 +02:00
|
|
|
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
|
2020-11-03 15:29:17 +01: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
|
|
|
|
|
|
2021-04-07 02:01:52 +02:00
|
|
|
def sp_write_file(self, sp, usedMODS, lvs=False, trim=False):
|
2020-04-03 22:39:54 +02:00
|
|
|
"""
|
|
|
|
|
Recursive spice subcircuit write;
|
2021-04-07 02:01:52 +02:00
|
|
|
Writes the spice subcircuit from the library or the dynamically generated one.
|
|
|
|
|
Trim netlist is intended ONLY for bitcell arrays.
|
2020-04-03 22:39:54 +02:00
|
|
|
"""
|
2020-06-22 20:33:02 +02:00
|
|
|
|
2020-06-22 21:35:37 +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.
|
2020-11-03 15:29:17 +01:00
|
|
|
|
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)
|
2021-04-07 02:01:52 +02:00
|
|
|
i.sp_write_file(sp, usedMODS, lvs, trim)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
if len(self.insts) == 0:
|
|
|
|
|
return
|
|
|
|
|
if self.pins == []:
|
|
|
|
|
return
|
2018-01-30 00:25:15 +01:00
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
# write out the first spice line (the subcircuit)
|
2020-11-03 01:00:16 +01:00
|
|
|
sp.write("\n.SUBCKT {0} {1}\n".format(self.cell_name,
|
2016-11-08 18:57:35 +01:00
|
|
|
" ".join(self.pins)))
|
|
|
|
|
|
2020-11-12 21:12:53 +01:00
|
|
|
# write a PININFO line
|
2020-11-12 23:33:42 +01:00
|
|
|
pin_info = "*.PININFO"
|
2020-11-12 21:12:53 +01:00
|
|
|
for pin in self.pins:
|
|
|
|
|
if self.pin_type[pin] == "INPUT":
|
|
|
|
|
pin_info += " {0}:I".format(pin)
|
|
|
|
|
elif self.pin_type[pin] == "OUTPUT":
|
|
|
|
|
pin_info += " {0}:O".format(pin)
|
|
|
|
|
else:
|
|
|
|
|
pin_info += " {0}:B".format(pin)
|
|
|
|
|
sp.write(pin_info + "\n")
|
|
|
|
|
|
|
|
|
|
# Also write pins as comments
|
2019-07-17 02:30:31 +02:00
|
|
|
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]))
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-01-24 02:27:15 +01:00
|
|
|
for line in self.comments:
|
|
|
|
|
sp.write("* {}\n".format(line))
|
2020-11-03 15:29:17 +01:00
|
|
|
|
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):
|
2020-11-03 01:00:16 +01:00
|
|
|
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.cell_name,
|
2016-11-08 18:57:35 +01:00
|
|
|
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
|
2021-11-22 19:51:40 +01:00
|
|
|
|
2020-06-22 21:35:37 +02:00
|
|
|
# Instance with no devices in it needs no subckt/instance
|
|
|
|
|
if self.insts[i].mod.no_instances:
|
|
|
|
|
continue
|
2021-11-22 19:51:40 +01:00
|
|
|
|
2021-04-07 02:01:52 +02:00
|
|
|
# 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("* ")
|
2021-11-22 19:51:40 +01:00
|
|
|
|
2021-04-07 02:01:52 +02:00
|
|
|
if lvs and hasattr(self.insts[i].mod, "lvs_device"):
|
2020-04-03 23:06:56 +02:00
|
|
|
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"):
|
2018-01-30 00:25:15 +01:00
|
|
|
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]),
|
2020-11-03 01:00:16 +01:00
|
|
|
self.insts[i].mod.cell_name))
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2020-11-03 01:00:16 +01:00
|
|
|
sp.write(".ENDS {0}\n".format(self.cell_name))
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
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))
|
2021-04-07 02:01:52 +02:00
|
|
|
if lvs and hasattr(self, "lvs"):
|
2020-04-03 22:39:54 +02:00
|
|
|
sp.write("\n".join(self.lvs))
|
|
|
|
|
else:
|
|
|
|
|
sp.write("\n".join(self.spice))
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
sp.write("\n")
|
|
|
|
|
|
2021-04-07 02:01:52 +02:00
|
|
|
def sp_write(self, spname, lvs=False, trim=False):
|
2016-11-08 18:57:35 +01:00
|
|
|
"""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()
|
2021-04-07 02:01:52 +02:00
|
|
|
self.sp_write_file(spfile, usedMODS, lvs=lvs, trim=trim)
|
2020-04-03 22:39:54 +02:00
|
|
|
del usedMODS
|
|
|
|
|
spfile.close()
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2021-07-21 21:24:08 +02:00
|
|
|
def cacti_delay(self, corner, inrisetime, c_load, cacti_params):
|
2021-07-07 22:22:30 +02:00
|
|
|
"""Generalization of how Cacti determines the delay of a gate"""
|
2021-07-21 21:24:08 +02:00
|
|
|
self.cacti_params = cacti_params
|
2021-07-07 22:22:30 +02:00
|
|
|
# Get the r_on the the tx
|
2021-07-12 22:02:22 +02:00
|
|
|
rd = self.get_on_resistance()
|
2021-12-17 19:21:34 +01:00
|
|
|
# Calculate the intrinsic capacitance
|
2021-07-12 22:02:22 +02:00
|
|
|
c_intrinsic = self.get_intrinsic_capacitance()
|
2021-08-02 04:25:54 +02:00
|
|
|
# Get wire values
|
|
|
|
|
c_wire = self.module_wire_c()
|
|
|
|
|
r_wire = self.module_wire_r()
|
2021-08-17 07:58:26 +02:00
|
|
|
|
2021-08-05 01:10:27 +02:00
|
|
|
tf = rd*(c_intrinsic+c_load+c_wire)+r_wire*(c_load+c_wire/2)
|
2021-09-08 00:56:27 +02:00
|
|
|
extra_param_dict = {}
|
|
|
|
|
extra_param_dict['vdd'] = corner[1] #voltage is second in PVT corner
|
|
|
|
|
extra_param_dict['load'] = c_wire+c_intrinsic+c_load #voltage is second in PVT corner
|
2021-09-01 23:27:13 +02:00
|
|
|
this_delay = self.cacti_rc_delay(inrisetime, tf, 0.5, 0.5, True, extra_param_dict)
|
2021-07-13 00:48:47 +02:00
|
|
|
inrisetime = this_delay / (1.0 - 0.5)
|
2021-07-07 22:22:30 +02:00
|
|
|
return delay_data(this_delay, inrisetime)
|
|
|
|
|
|
2019-03-04 09:42:18 +01:00
|
|
|
def analytical_delay(self, corner, slew, load=0.0):
|
2017-05-30 21:50:07 +02:00
|
|
|
"""Inform users undefined delay module while building new modules"""
|
2020-11-03 15:29:17 +01:00
|
|
|
|
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
|
2019-08-07 10:50:48 +02:00
|
|
|
relative_cap = logical_effort.convert_farad_to_relative_c(load)
|
|
|
|
|
stage_effort = self.get_stage_effort(relative_cap)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-08-07 10:50:48 +02:00
|
|
|
# If it fails, then keep running with a valid object.
|
2020-06-22 20:33:02 +02:00
|
|
|
if not stage_effort:
|
2019-08-07 10:50:48 +02:00
|
|
|
return delay_data(0.0, 0.0)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-08-07 10:50:48 +02:00
|
|
|
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
|
2019-08-07 10:50:48 +02:00
|
|
|
return delay_data(corner_delay, corner_slew)
|
|
|
|
|
|
2021-08-02 04:25:54 +02:00
|
|
|
def module_wire_c(self):
|
|
|
|
|
"""All devices assumed to have ideal capacitance (0).
|
2021-12-17 19:21:34 +01:00
|
|
|
Non-ideal cases should have this function re-defined.
|
2021-08-02 04:25:54 +02:00
|
|
|
"""
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
def module_wire_r(self):
|
|
|
|
|
"""All devices assumed to have ideal resistance (0).
|
2021-12-17 19:21:34 +01:00
|
|
|
Non-ideal cases should have this function re-defined.
|
2021-08-02 04:25:54 +02:00
|
|
|
"""
|
|
|
|
|
return 0
|
|
|
|
|
|
2019-09-03 20:50:39 +02:00
|
|
|
def get_stage_effort(self, cout, inp_is_rise=True):
|
2019-08-07 10:50:48 +02:00
|
|
|
"""Inform users undefined delay module while building new modules"""
|
|
|
|
|
debug.warning("Design Class {0} logical effort function needs to be defined"
|
2017-05-30 21:50:07 +02:00
|
|
|
.format(self.__class__.__name__))
|
|
|
|
|
debug.warning("Class {0} name {1}"
|
2019-09-03 20:50:39 +02:00
|
|
|
.format(self.__class__.__name__,
|
2020-11-03 01:00:16 +01:00
|
|
|
self.cell_name))
|
2020-04-03 20:37:06 +02:00
|
|
|
return None
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2021-07-12 22:02:22 +02:00
|
|
|
def get_on_resistance(self):
|
|
|
|
|
"""Inform users undefined delay module while building new modules"""
|
|
|
|
|
debug.warning("Design Class {0} on resistance function needs to be defined"
|
|
|
|
|
.format(self.__class__.__name__))
|
|
|
|
|
debug.warning("Class {0} name {1}"
|
|
|
|
|
.format(self.__class__.__name__,
|
|
|
|
|
self.cell_name))
|
|
|
|
|
return 0
|
|
|
|
|
|
2021-07-21 21:24:08 +02:00
|
|
|
def get_input_capacitance(self):
|
|
|
|
|
"""Inform users undefined delay module while building new modules"""
|
|
|
|
|
debug.warning("Design Class {0} input capacitance function needs to be defined"
|
|
|
|
|
.format(self.__class__.__name__))
|
|
|
|
|
debug.warning("Class {0} name {1}"
|
|
|
|
|
.format(self.__class__.__name__,
|
|
|
|
|
self.cell_name))
|
|
|
|
|
return 0
|
|
|
|
|
|
2021-07-12 22:02:22 +02:00
|
|
|
def get_intrinsic_capacitance(self):
|
|
|
|
|
"""Inform users undefined delay module while building new modules"""
|
|
|
|
|
debug.warning("Design Class {0} intrinsic capacitance function needs to be defined"
|
|
|
|
|
.format(self.__class__.__name__))
|
|
|
|
|
debug.warning("Class {0} name {1}"
|
|
|
|
|
.format(self.__class__.__name__,
|
|
|
|
|
self.cell_name))
|
|
|
|
|
return 0
|
|
|
|
|
|
2019-08-08 10:57:04 +02:00
|
|
|
def get_cin(self):
|
|
|
|
|
"""Returns input load in Femto-Farads. All values generated using
|
|
|
|
|
relative capacitance function then converted based on tech file parameter."""
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-08-08 10:57:04 +02:00
|
|
|
# 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)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-08-08 10:57:04 +02:00
|
|
|
def input_load(self):
|
|
|
|
|
"""Inform users undefined relative capacitance functions used for analytical delays."""
|
2021-07-21 21:24:08 +02:00
|
|
|
debug.warning("Design Class {0} input load function needs to be defined"
|
2019-08-08 10:57:04 +02:00
|
|
|
.format(self.__class__.__name__))
|
|
|
|
|
debug.warning("Class {0} name {1}"
|
2020-04-03 20:37:06 +02:00
|
|
|
.format(self.__class__.__name__,
|
2020-11-03 01:00:16 +01:00
|
|
|
self.cell_name))
|
2020-04-03 20:37:06 +02:00
|
|
|
return 0
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2021-12-17 19:21:34 +01:00
|
|
|
def cacti_rc_delay(self,
|
2021-08-26 01:12:05 +02:00
|
|
|
inputramptime, # input rise time
|
|
|
|
|
tf, # time constant of gate
|
|
|
|
|
vs1, # threshold voltage
|
|
|
|
|
vs2, # threshold voltage
|
2021-09-01 23:27:13 +02:00
|
|
|
rise, # whether input rises or fall
|
2021-12-17 19:21:34 +01:00
|
|
|
extra_param_dict=None):
|
2021-09-08 00:56:27 +02:00
|
|
|
"""By default, CACTI delay uses horowitz for gate delay.
|
2021-08-26 01:12:05 +02:00
|
|
|
Can be overriden in cases like bitline if equation is different.
|
|
|
|
|
"""
|
|
|
|
|
return self.horowitz(inputramptime, tf, vs1, vs2, rise)
|
2021-12-17 19:21:34 +01:00
|
|
|
|
|
|
|
|
def horowitz(self,
|
2021-07-13 00:48:47 +02:00
|
|
|
inputramptime, # input rise time
|
2021-07-07 22:22:30 +02:00
|
|
|
tf, # time constant of gate
|
|
|
|
|
vs1, # threshold voltage
|
|
|
|
|
vs2, # threshold voltage
|
|
|
|
|
rise): # whether input rises or fall
|
2021-07-13 00:48:47 +02:00
|
|
|
|
2021-07-07 22:22:30 +02:00
|
|
|
if inputramptime == 0 and vs1 == vs2:
|
|
|
|
|
return tf * (-math.log(vs1) if vs1 < 1 else math.log(vs1))
|
|
|
|
|
|
|
|
|
|
a = inputramptime / tf
|
2021-07-13 00:48:47 +02:00
|
|
|
if rise == True:
|
|
|
|
|
b = 0.5
|
|
|
|
|
td = tf * math.sqrt(math.log(vs1)*math.log(vs1) + 2*a*b*(1.0 - vs1)) + tf*(math.log(vs1) - math.log(vs2))
|
2021-07-07 22:22:30 +02:00
|
|
|
|
|
|
|
|
else:
|
2021-07-13 00:48:47 +02:00
|
|
|
b = 0.4
|
|
|
|
|
td = tf * math.sqrt(math.log(1.0 - vs1)*math.log(1.0 - vs1) + 2*a*b*(vs1)) + tf*(math.log(1.0 - vs1) - math.log(1.0 - vs2))
|
2021-07-07 22:22:30 +02:00
|
|
|
|
|
|
|
|
return td
|
2021-12-17 19:21:34 +01:00
|
|
|
|
|
|
|
|
def tr_r_on(self, width, is_nchannel, stack, _is_cell):
|
|
|
|
|
|
2021-07-21 21:24:08 +02:00
|
|
|
restrans = self.cacti_params["r_nch_on"] if is_nchannel else self.cacti_params["r_pch_on"]
|
2021-07-07 22:22:30 +02:00
|
|
|
return stack * restrans / width
|
|
|
|
|
|
2021-07-21 21:24:08 +02:00
|
|
|
def gate_c(self, width):
|
2021-12-17 19:21:34 +01:00
|
|
|
|
2021-07-13 00:48:47 +02:00
|
|
|
return (tech.spice["c_g_ideal"] + tech.spice["c_overlap"] + 3*tech.spice["c_fringe"])*width +\
|
2021-07-21 21:24:08 +02:00
|
|
|
tech.drc["minlength_channel"]*tech.spice["cpolywire"]
|
2021-12-17 19:21:34 +01:00
|
|
|
|
2021-07-13 00:48:47 +02:00
|
|
|
def drain_c_(self,
|
|
|
|
|
width,
|
2021-07-07 22:22:30 +02:00
|
|
|
stack,
|
2021-07-21 23:59:02 +02:00
|
|
|
folds):
|
2021-07-07 22:22:30 +02:00
|
|
|
|
2021-07-21 23:59:02 +02:00
|
|
|
c_junc_area = tech.spice["c_junc"]
|
|
|
|
|
c_junc_sidewall = tech.spice["c_junc_sw"]
|
|
|
|
|
c_fringe = 2*tech.spice["c_overlap"]
|
|
|
|
|
c_overlap = 2*tech.spice["c_fringe"]
|
2021-07-07 22:22:30 +02:00
|
|
|
drain_C_metal_connecting_folded_tr = 0
|
2021-12-17 19:21:34 +01:00
|
|
|
|
2021-07-21 23:59:02 +02:00
|
|
|
w_folded_tr = width/folds
|
|
|
|
|
num_folded_tr = folds
|
2021-12-17 19:21:34 +01:00
|
|
|
|
2021-07-27 23:31:22 +02:00
|
|
|
# Re-created some logic contact to get minwidth as importing the contact
|
|
|
|
|
# module causes a failure
|
|
|
|
|
if "minwidth_contact" in tech.drc:
|
|
|
|
|
contact_width = tech.drc["minwidth_contact"]
|
|
|
|
|
elif "minwidth_active_contact" in tech.drc:
|
|
|
|
|
contact_width = tech.drc["minwidth_active_contact"]
|
|
|
|
|
else:
|
|
|
|
|
debug.warning("Undefined minwidth_contact in tech.")
|
|
|
|
|
contact_width = 0
|
|
|
|
|
|
2021-07-13 00:48:47 +02:00
|
|
|
# only for drain
|
2021-07-27 23:31:22 +02:00
|
|
|
total_drain_w = (contact_width + 2 * tech.drc["active_contact_to_gate"]) +\
|
|
|
|
|
(stack - 1) * tech.drc["poly_to_poly"]
|
2021-07-07 22:22:30 +02:00
|
|
|
drain_h_for_sidewall = w_folded_tr
|
|
|
|
|
total_drain_height_for_cap_wrt_gate = w_folded_tr + 2 * w_folded_tr * (stack - 1)
|
|
|
|
|
if num_folded_tr > 1:
|
2021-07-27 23:31:22 +02:00
|
|
|
total_drain_w += (num_folded_tr - 2) * (contact_width + 2 * tech.drc["active_contact_to_gate"]) +\
|
|
|
|
|
(num_folded_tr - 1) * ((stack - 1) * tech.drc["poly_to_poly"])
|
2021-07-07 22:22:30 +02:00
|
|
|
|
|
|
|
|
if num_folded_tr%2 == 0:
|
|
|
|
|
drain_h_for_sidewall = 0
|
2021-12-17 19:21:34 +01:00
|
|
|
|
2021-07-07 22:22:30 +02:00
|
|
|
total_drain_height_for_cap_wrt_gate *= num_folded_tr
|
2021-07-21 23:59:02 +02:00
|
|
|
drain_C_metal_connecting_folded_tr = tech.spice["wire_c_per_um"] * total_drain_w
|
2021-12-17 19:21:34 +01:00
|
|
|
|
2021-07-07 22:22:30 +02:00
|
|
|
|
|
|
|
|
drain_C_area = c_junc_area * total_drain_w * w_folded_tr
|
|
|
|
|
drain_C_sidewall = c_junc_sidewall * (drain_h_for_sidewall + 2 * total_drain_w)
|
|
|
|
|
drain_C_wrt_gate = (c_fringe + c_overlap) * total_drain_height_for_cap_wrt_gate
|
|
|
|
|
|
|
|
|
|
return drain_C_area + drain_C_sidewall + drain_C_wrt_gate + drain_C_metal_connecting_folded_tr
|
|
|
|
|
|
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
|
2017-05-30 21:50:07 +02:00
|
|
|
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
|
2019-04-09 21:26:54 +02:00
|
|
|
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-11-03 15:29:17 +01:00
|
|
|
|
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-05-30 21:50:07 +02:00
|
|
|
|
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.
|
2017-05-30 21:50:07 +02:00
|
|
|
# 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)
|
2017-05-30 21:50:07 +02:00
|
|
|
|
2019-04-09 21:26:54 +02:00
|
|
|
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))
|
2019-04-09 21:26:54 +02:00
|
|
|
volt_mult = self.get_voltage_delay_factor(vdd)
|
|
|
|
|
temp_mult = self.get_temp_delay_factor(temp)
|
|
|
|
|
return delay * proc_mult * volt_mult * temp_mult
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-03-04 09:42:18 +01:00
|
|
|
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)
|
2019-03-04 09:42:18 +01:00
|
|
|
return proc_factors
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-03-04 09:42:18 +01:00
|
|
|
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
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-03-04 09:42:18 +01:00
|
|
|
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-05-30 21:50:07 +02:00
|
|
|
|
2017-07-06 17:42:25 +02:00
|
|
|
def return_delay(self, delay, slew):
|
|
|
|
|
return delay_data(delay, slew)
|
2017-05-30 21:50:07 +02:00
|
|
|
|
2020-04-03 20:37:06 +02:00
|
|
|
def generate_rc_net(self, lump_num, wire_length, wire_width):
|
2017-05-30 21:50:07 +02:00
|
|
|
return wire_spice_model(lump_num, wire_length, wire_width)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-03-05 04:27:53 +01:00
|
|
|
def calc_dynamic_power(self, corner, c, freq, swing=1.0):
|
2020-04-03 20:37:06 +02:00
|
|
|
"""
|
2019-03-05 04:27:53 +01: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-11-03 15:29:17 +01:00
|
|
|
|
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))
|
2019-03-05 04:27:53 +01:00
|
|
|
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-11-03 15:29:17 +01:00
|
|
|
|
2020-04-03 20:37:06 +02:00
|
|
|
return power_dyn
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-02-22 09:15:55 +01:00
|
|
|
def return_power(self, dynamic=0.0, leakage=0.0):
|
2018-02-22 04:51:21 +01:00
|
|
|
return power_data(dynamic, leakage)
|
2017-05-30 21:50:07 +02:00
|
|
|
|
2020-08-31 23:36:13 +02:00
|
|
|
def find_aliases(self, inst_name, port_nets, path_nets, alias, alias_mod, exclusion_set=None):
|
2020-09-29 19:26:31 +02:00
|
|
|
"""
|
|
|
|
|
Given a list of nets, will compare the internal alias of a mod to determine
|
|
|
|
|
if the nets have a connection to this mod's net (but not inst).
|
2020-08-31 23:36:13 +02:00
|
|
|
"""
|
|
|
|
|
if not exclusion_set:
|
|
|
|
|
exclusion_set = set()
|
|
|
|
|
try:
|
|
|
|
|
self.name_dict
|
|
|
|
|
except AttributeError:
|
|
|
|
|
self.name_dict = {}
|
|
|
|
|
self.build_names(self.name_dict, inst_name, port_nets)
|
|
|
|
|
aliases = []
|
|
|
|
|
for net in path_nets:
|
|
|
|
|
net = net.lower()
|
|
|
|
|
int_net = self.name_dict[net]['int_net']
|
|
|
|
|
int_mod = self.name_dict[net]['mod']
|
2021-06-17 02:04:02 +02:00
|
|
|
|
2020-08-31 23:36:13 +02:00
|
|
|
if int_mod.is_net_alias(int_net, alias, alias_mod, exclusion_set):
|
|
|
|
|
aliases.append(net)
|
|
|
|
|
return aliases
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2020-08-31 23:36:13 +02:00
|
|
|
def is_net_alias(self, known_net, net_alias, mod, exclusion_set):
|
2020-09-29 19:26:31 +02:00
|
|
|
"""
|
|
|
|
|
Checks if the alias_net in input mod is the same as the input net for this mod (self).
|
|
|
|
|
"""
|
2020-08-31 23:36:13 +02:00
|
|
|
if self in exclusion_set:
|
|
|
|
|
return False
|
|
|
|
|
# Check ports of this mod
|
|
|
|
|
for pin in self.pins:
|
|
|
|
|
if self.is_net_alias_name_check(known_net, pin, net_alias, mod):
|
|
|
|
|
return True
|
|
|
|
|
# Check connections of all other subinsts
|
|
|
|
|
mod_set = set()
|
|
|
|
|
for subinst, inst_conns in zip(self.insts, self.conns):
|
|
|
|
|
for inst_conn, mod_pin in zip(inst_conns, subinst.mod.pins):
|
|
|
|
|
if self.is_net_alias_name_check(known_net, inst_conn, net_alias, mod):
|
|
|
|
|
return True
|
|
|
|
|
elif inst_conn.lower() == known_net.lower() and subinst.mod not in mod_set:
|
|
|
|
|
if subinst.mod.is_net_alias(mod_pin, net_alias, mod, exclusion_set):
|
|
|
|
|
return True
|
|
|
|
|
mod_set.add(subinst.mod)
|
|
|
|
|
return False
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2020-08-31 23:36:13 +02:00
|
|
|
def is_net_alias_name_check(self, parent_net, child_net, alias_net, mod):
|
2020-09-29 19:26:31 +02:00
|
|
|
"""
|
|
|
|
|
Utility function for checking single net alias.
|
|
|
|
|
"""
|
2020-08-31 23:36:13 +02:00
|
|
|
return self == mod and \
|
|
|
|
|
child_net.lower() == alias_net.lower() and \
|
|
|
|
|
parent_net.lower() == alias_net.lower()
|