blob: c14b0b7e4208a7332c15fda7c46867c1e2e0b3c9 [file] [log] [blame]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2017-2020 The Project X-Ray Authors.
#
# Use of this source code is governed by a ISC-style
# license that can be found in the LICENSE file or at
# https://opensource.org/licenses/ISC
#
# SPDX-License-Identifier: ISC
""" Route timing delay definitions.
Routing delay is formed from two parts in this model:
- Intristic delay of the element
- Capactive loading delay of the net
Intristic delay is a time value (e.g. nanoseconds), does not vary based on
routing fanout. It does vary based on the PVT (process, voltage, temperature)
corner. PvtCorner and IntristicDelay objects are used to model intristic
delay of elements.
Capactive loading is the elmore delay from the RC tree formed by interconnect.
The RC tree is made up of 5 types of RC nodes:
|Element type |Object |Intrinsic delays?|Output resistance?|Capacitance type |
|----------------------|--------------|-----------------|------------------|--------------------|
|Site output pin |Outpin |Yes |Yes |N/A |
|Buffered switch |Buffer |Yes |Yes |Internal capacitance|
|Pass-transistor switch|PassTransistor|Yes |Yes |N/A |
|Wire |Wire |No |Yes |Pi model |
|Site input pin |Inpin |Yes |No |Input capacitance |
The elmore delay is the RC tree formed by these 5 components. Out pins and
buffer switches are the roots of the elmore tree. Buffer switches
and inpins are leafs of the elmore tree. Wires and pass-transistor switches are
nodes in the tree. Wires share their capacitance upstream and downstream using
a pi-model.
Example timing tree:
+------+
|Outpin|
+--+---+
|
|
v
+--+--+
|Wire |
+--+--+
|
+-----------------+
| |
+--+---+ +-------+------+
|Buffer| |PassTransistor|
+--+---+ +------+-------+
| |
v v
+--+-+ +--+-+
|Wire| |Wire|
+--+-+ +--+-+
| |
v v
+--+--+ +--+--+
|Inpin| |Inpin|
+-----+ +-----+
Note on units:
The timing model operates on the following types of units:
- Time
- Resistance
- Capacitance
For a consistent unit set, the following equation must be satisfied:
1 Resistance unit * 1 Capacitance unit = 1 Time unit
The SI unit set would be:
- Time = seconds
- Resistance = Ohms
- Capacitance = Farads
However as long as the scale factors are consistent, the model will work
with other unit combinations. For example:
- Time = nanoseconds (1e-9 seconds)
- Resistance = milliOhms (1e-3 Ohms)
- Capacitance = microFarads (1e-6 Farads)
(1e-3 * 1e-6) (Ohms * Farads) does equal (1e-9) seconds.
"""
import enum
from collections import namedtuple
class PvtCorner(enum.Enum):
""" Process/voltage/temperature corner definitions. """
# Corner where device operates with fastest intristic delays.
FAST = "FAST"
# Corner where device operates with slowest intristic delays.
SLOW = "SLOW"
def __lt__(self, other):
if self.__class__ is other.__class__:
return self.value < other.value
return NotImplemented
class IntristicDelay(namedtuple('IntristicDelay', 'min max')):
""" An intristic delay instance.
Represents is the intristic delay through an element (e.g. a site pin or
interconnect pip).
The intristic delay of an element is generally modelled at a particular
"corner" of a design. The "corner" generally speaking is modelled over
process, voltage and temperature PVT. The IntristicDelay object
reperesents the minimum or maximum delay through all instances of the
element at 1 corner.
Attributes
----------
min : float
Minimum instrinsic delay (nsec)
max : float
Maximum instrinsic delay (nsec)
"""
class RcElement(namedtuple('RcElement', 'resistance capacitance')):
""" One part of an RcNode, embedded within an RcTree.
Attributes
----------
resistance : float
Resistance of element
capacitance : float
Capacitance of element
"""
pass
class hashabledict(dict):
""" Immutable version of dictionary with hash support. """
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.hash = hash(tuple(sorted(self.items())))
def setdefault(self, *args, **kwargs):
raise NotImplementedError("hashabledict cannot be mutated.")
def __setitem__(self, *args, **kwargs):
raise NotImplementedError("hashabledict cannot be mutated.")
def update(self, *args, **kwargs):
raise NotImplementedError("hashabledict cannot be mutated.")
def __hash__(self):
return self.hash
def fast_slow_tuple_to_corners(arr):
""" Convert delay 4-tuple into two IntristicDelay objects.
Returns
-------
corners : dict of PvtCorner to IntristicDelay
Dictionary keys of FAST and SLOW, mapping to the instrinsic delay
for each corner.
"""
fast_min, fast_max, slow_min, slow_max = map(float, arr)
return hashabledict(
{
PvtCorner.FAST: IntristicDelay(
min=fast_min,
max=fast_max,
),
PvtCorner.SLOW: IntristicDelay(
min=slow_min,
max=slow_max,
),
})
class TimingNode(object):
""" Base class for timing node models.
"""
def get_intrinsic_delays(self):
""" Returns Intristic delays (if any) timing node.
Returns
-------
Dictionary of PvtCorner to Intristic. Is None if node has no intristic
delay.
"""
pass
def get_rc_delay(self):
""" Return portion of net delay due to elmore (RC) delay at this node.
Must be called after propigate_delays has been called on the Outpin
object of this tree.
"""
pass
def get_downstream_cap(self):
""" Returns downstream capacitance at this node.
Must be called after propigate_delays has been called on the Outpin
object of this tree.
"""
pass
def propigate_downstream_capacitance(self, math):
""" Returns capacitance visible to parent of this node.
Must call propigate_downstream_capacitance on all children of this node.
Should save downstream capacitance visible to this node's output
(if any) to be returned in the get_downstream_cap method.
"""
pass
class DownstreamNode(TimingNode):
""" All non-root TimingNode's are DownstreamNode's.
"""
def propigate_delays(self, elements, math):
""" Propigates upstream delay elements to children of the tree.
Must call propigated_delays on all children of this node, and add this
node to elements.
Arguments
---------
elements : list of TimingNode's
List of delay nodes between root of this tree and this node.
math : MathModel
Math model to use to compute delays
"""
pass
class Outpin(TimingNode):
""" Represents a site output pin.
Outpin object is the root of the timing tree. Once tree is built with
set_sink_wire and Wire.add_child methods, propigate_delays should be
invoked to estabilish model.
Arguments
---------
resistance
Drive resistance in elmore delay model
delays
Intristic delays on output pin.
"""
def __init__(self, resistance, delays):
self.resistance = resistance
self.delays = delays
self.sink_wire = None
self.downstream_cap = None
self.rc_delay = None
def set_sink_wire(self, wire):
""" Sets sink wire for this output pin.
An output pin always sinks to exactly 1 wire.
This method must be called prior to calling propigate_delays method
on this object.
Arguments
---------
wire : Wire object
Sink wire for this output pin.
"""
self.sink_wire = wire
def propigate_downstream_capacitance(self, math):
assert self.sink_wire is not None
self.downstream_cap = self.sink_wire.propigate_downstream_capacitance(
math)
self.rc_delay = math.multiply(self.downstream_cap, self.resistance)
def propigate_delays(self, math):
""" Propigate delays throughout tree using specified math model.
Must be called after elmore tree is estabilished.
Arguments
---------
math : MathModel object
Math model used when doing timing computations.
"""
self.propigate_downstream_capacitance(math)
self.sink_wire.propigate_delays([self], math)
self.rc_delay = math.multiply(self.resistance, self.downstream_cap)
def get_intrinsic_delays(self):
return self.delays
def get_rc_delay(self):
assert self.rc_delay is not None
return self.rc_delay
def get_downstream_cap(self):
assert self.downstream_cap is not None
return self.downstream_cap
class Inpin(DownstreamNode):
""" Represents a site input pin.
Represents leaf of timing model. Once model is connected and delays
are propigate (by calling Outpin,propigated_delays), get_delays will
correctly return the list of delay elements from the root to this leaf.
Arguments
---------
capacitance
Pin capacitance for input pin.
delays
Intristic delays on input pin.
"""
def __init__(self, capacitance, delays, name=None):
self.capacitance = capacitance
self.delays = delays
self.propigated_delays = None
self.name = name
def get_intrinsic_delays(self):
return self.delays
def get_rc_delay(self):
return None
def get_downstream_cap(self):
return None
def propigate_downstream_capacitance(self, math):
return self.capacitance
def propigate_delays(self, elements, math):
self.propigated_delays = list(elements)
def get_delays(self):
""" Return list of delay models that make up the delay for this pin.
The sum of all delay elements (both intristic and RC) is the net
delay from the output pin to this input pin.
"""
return self.propigated_delays + [self]
class Wire(DownstreamNode):
""" Represents a wire in the timing model.
Wires must be connected to an upstream driver model (Outpin, Buffer,
PassTransistor objects) with set_sink_wire, and add_child must be called
attaching output nodes (Buffer, PassTransistor, Inpin objects).
Arguments
---------
rc_elements : List of RcElement
Resistance and capacitance of this wire.
math : MathModel
Math model used to compute lumped resistance and capacitance.
"""
def __init__(self, rc_elements, math):
self.resistance = math.sum(elem.resistance for elem in rc_elements)
self.capacitance = math.sum(elem.capacitance for elem in rc_elements)
self.children = []
self.downstream_cap = None
self.propigated_delays = None
self.rc_delay = None
def add_child(self, child):
""" Add a child node to this wire.
Call this method as needed prior to calling propigate_delays on the
root Outpin object.
Arguments
---------
child : Buffer or PassTransistor or Inpin
Adds child load to this wire.
"""
self.children.append(child)
def propigate_downstream_capacitance(self, math):
downstream_cap = math.sum(
child.propigate_downstream_capacitance(math)
for child in self.children)
# Pi-model is definied such that wire resistance only sees half of the
# wire capacitance.
self.downstream_cap = math.plus(
math.divide(self.capacitance, 2), downstream_cap)
# Upstream seems all of the wires capacitance
return math.plus(downstream_cap, self.capacitance)
def propigate_delays(self, elements, math):
self.propigated_delays = list(elements)
for child in self.children:
child.propigate_delays(self.propigated_delays + [self], math)
self.rc_delay = math.multiply(self.resistance, self.downstream_cap)
def get_intrinsic_delays(self):
return None
def get_rc_delay(self):
assert self.rc_delay is not None
return self.rc_delay
def get_downstream_cap(self):
assert self.downstream_cap is not None
return self.downstream_cap
class Buffer(DownstreamNode):
""" Represents an isolating switch.
The internal_capacitance model is such that the upstream node only sees
the capacitance of this node when the switch is enabled. Therefore, only
active buffers should be included in the model.
Arguments
---------
internal_capacitance
Capacitance seen by upstream node when this buffer is enabled.
drive_resistance
Driver resistance used for computing elmore delay.
delays : Dictionary of PvtCorner to IntristicDelay
Delay through switch
"""
def __init__(self, internal_capacitance, drive_resistance, delays):
self.internal_capacitance = internal_capacitance
self.drive_resistance = drive_resistance
self.delays = delays
self.downstream_cap = None
self.rc_delay = None
def set_sink_wire(self, wire):
""" Sets sink wire for this output pin.
An output pin always sinks to exactly 1 wire.
This method must be called prior to calling propigate_delays method
on the root Outpin object of this tree.
Arguments
---------
wire : Wire object
Sink wire for this output pin.
"""
self.sink_wire = wire
def propigate_downstream_capacitance(self, math):
assert self.sink_wire is not None
self.downstream_cap = self.sink_wire.propigate_downstream_capacitance(
math)
return self.internal_capacitance
def propigate_delays(self, elements, math):
self.propigated_delays = list(elements)
assert self.sink_wire is not None
self.sink_wire.propigate_delays(self.propigated_delays + [self], math)
self.rc_delay = math.multiply(
self.downstream_cap, self.drive_resistance)
def get_intrinsic_delays(self):
return self.delays
def get_rc_delay(self):
assert self.rc_delay is not None
return self.rc_delay
def get_downstream_cap(self):
assert self.downstream_cap is not None
return self.downstream_cap
class PassTransistor(DownstreamNode):
""" Represents a non-isolating switch.
Arguments
---------
drive_resistance
Driver resistance used for computing elmore delay.
delays : Dictionary of PvtCorner to IntristicDelay
Delay through switch.
"""
def __init__(self, drive_resistance, delays):
self.drive_resistance = drive_resistance
self.delays = delays
self.sink_wire = None
self.downstream_cap = None
self.rc_delay = None
def set_sink_wire(self, wire):
""" Sets sink wire for this output pin.
An output pin always sinks to exactly 1 wire.
This method must be called prior to calling propigate_delays method
on the root Outpin object of this tree.
Arguments
---------
wire : Wire object
Sink wire for this output pin.
"""
self.sink_wire = wire
def propigate_downstream_capacitance(self, math):
assert self.sink_wire is not None
self.downstream_cap = self.sink_wire.propigate_downstream_capacitance(
math)
return self.downstream_cap
def propigate_delays(self, elements, math):
self.propigated_delays = list(elements)
assert self.sink_wire is not None
self.sink_wire.propigate_delays(self.propigated_delays + [self], math)
self.rc_delay = math.multiply(
self.downstream_cap, self.drive_resistance)
def get_intrinsic_delays(self):
return self.delays
def get_rc_delay(self):
assert self.rc_delay is not None
return self.rc_delay
def get_downstream_cap(self):
assert self.downstream_cap is not None
return self.downstream_cap