Merge pull request #821 from litghost/timing_model_objects

Add objects and docstrings for timing model.
This commit is contained in:
litghost 2019-05-09 14:39:58 -07:00 committed by GitHub
commit 02026c2d8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 873 additions and 27 deletions

65
prjxray/math_models.py Normal file
View File

@ -0,0 +1,65 @@
""" Math models are used to represent abstract operations for the timing models.
This is useful for creating excel workbooks that can update dynamically, or
generating a model with symbolic constants to be backsolved.
"""
class ExcelMathModel(object):
""" Math model used for outputting to an dyanmic Excel sheet. """
def __init__(self):
pass
def sum(self, elems):
sum_val = '(' + ' + '.join(elems) + ')'
if sum_val == '()':
return '0'
else:
return sum_val
def product(self, elems):
sum_val = '(' + ' * '.join(elems) + ')'
if sum_val == '()':
return '1'
else:
return sum_val
def plus(self, a, b):
return self.sum((a, b))
def divide(self, a, b):
return '({}/{})'.format(a, b)
def multiply(self, a, b):
return '({}*{})'.format(a, b)
def eval(self, elem):
return '=' + elem
def PythonMathModel(object):
""" Math model used for outputting values equalated immediately. """
def __init__(self):
pass
def sum(self, elems):
return sum(elems)
def product(self, elems):
v = 1.0
for elem in elems:
v *= elem
return v
def divide(self, a, b):
return a / b
def plus(self, a, b):
return a + b
def multiply(self, a, b):
return a * b
def eval(self, elem):
return elem

View File

@ -1,38 +1,251 @@
""" Database files available for a tile type. """
from collections import namedtuple
import json
from prjxray import lib
""" Database files available for a tile """
from prjxray.timing import fast_slow_tuple_to_corners, RcElement
TileDbs = namedtuple(
'TileDbs', 'segbits block_ram_segbits ppips mask tile_type')
Pip = namedtuple(
'Pip', 'name net_to net_from can_invert is_directional is_pseudo')
""" Site - Represents an instance of a site within a tile.
name - Name of site within tile, instance specific.
prefix - Prefix of site naming in Xilinx parlance.
type - What type of slice this instance presents.
pins - Instaces of site pins within this site and tile. This is an tuple of
SitePin tuples, and is specific to this instance of the site within
the tile.
class OutPinTiming(namedtuple('OutPinTiming', 'delays drive_resistance')):
""" Timing for site output pins.
"""
Site = namedtuple('Site', 'name prefix x y type site_pins')
""" SitePin - Tuple representing a site pin within a tile.
Attributes
----------
delays : dicts of PvtCorner to IntristicDelay
Intristic delay of output pin.
drive_resistance : float
Resistance of drive output pin (milliOhms).
Sites are generic based on type, however sites are instanced
within a tile 1 or more times. The SitePin contains both site type generic
information and tile type specific information.
"""
pass
name - Site type specific name. This name is expected to be the same for all
sites of the same type.
wire - Wire name within the tile. This name is site instance specific.
"""
SitePin = namedtuple('SitePin', 'name wire')
class InPinTiming(namedtuple('InPinTiming', 'delays capacitance')):
""" Timing for site input pins.
Attributes
----------
delays : dicts of PvtCorner to IntristicDelay
Intristic delay of input pin.
capacitance : float
Capacitance of input pints (microFarads).
"""
pass
class PipTiming(namedtuple('PipTiming',
'delays drive_resistance internal_capacitance')):
""" Timing for pips.
Attributes
----------
delays : dicts of PvtCorner to IntristicDelay
Intristic delay of pip.
internal_capacitance : float
Capacitance (microFarads) of pip (which is only seen if pip is used).
drive_resistance : float
Resistance of drive output pin (milliOhms).
"""
pass
class Pip(namedtuple(
'Pip',
('name', 'net_to', 'net_from', 'can_invert', 'is_directional', 'is_pseudo',
'is_pass_transistor', 'timing', 'backward_timing'))):
""" Pip information.
Attributes
----------
name : str
Name of pip
net_to : str
Name of output tile wire when pip is unidirectional.
net_from: str
Name of input tile wire when pip is unidirectional.
can_invert : bool
Can this pip invert the signal.
is_directional : bool
True if this pip is unidirectional, False if this pip is
unidirectional.
is_pseudo : bool
True if this pip is mark as a pseudo-pip.
is_pass_transistor : bool
True if this pip is non-isolating.
timing : PipTiming
Timing used when connecting net_from to net_to. This is the only
timing used when a pip is unidirectional.
May be None if timing information is not present in the database.
backward_timing : PipTiming
Timing used when connecting net_to to net_from. This is only used
if the pip is bidirectional.
May be None if timing information is not present in the database.
"""
pass
class Site(namedtuple('Site', 'name prefix x y type site_pins')):
""" Represents an instance of a site within a tile.
Attributes
----------
name : str
Name of site within tile, instance specific.
prefix : str
Prefix of site naming in Xilinx parlance.
type : str
What type of slice this instance presents.
site_pins : list of SitePin
Instaces of site pins within this site and tile. This is an tuple of
SitePin tuples, and is specific to this instance of the site within
the tile.
"""
class SitePin(namedtuple('SitePin', 'name wire timing')):
""" Tuple representing a site pin within a tile.
Sites are generic based on type, however sites are instanced
within a tile 1 or more times. The SitePin contains both site type generic
information and tile type specific information.
Attributes
----------
name : str
Site type specific name. This name is expected to be the same for
all sites of the same type.
wire : str
Wire name within the tile. This name is site instance specific.
timing : Either InPinTiming or OutPinTiming
Timing of site pin. May be None if database lacks timing information.
"""
WireInfo = namedtuple('WireInfo', 'pips sites')
# Conversion factor from database to internal units.
RESISTANCE_FACTOR = 1
CAPACITANCE_FACTOR = 1e3
def get_pip_timing(pip_timing_json):
""" Convert pip_timing_json JSON into PipTiming object.
Returns
-------
If timing information is not present for this pip, returns None.
If timing information is present, returns PipTiming. Some fields may be
None if the pip type lacks that field.
"""
if pip_timing_json is None:
return None
delays = None
if pip_timing_json.get('delay') is not None:
delays = fast_slow_tuple_to_corners(pip_timing_json.get('delay'))
in_cap = pip_timing_json.get('in_cap')
if in_cap is not None:
in_cap = float(in_cap) / CAPACITANCE_FACTOR
else:
in_cap = 0
res = pip_timing_json.get('res')
if res is not None:
res = float(res) / RESISTANCE_FACTOR
else:
res = 0
return PipTiming(
delays=delays,
drive_resistance=res,
internal_capacitance=in_cap,
)
def get_site_pin_timing(site_pin_info):
""" Convert site_pin_info JSON into InPinTiming or OutPinTiming object.
Returns
-------
If timing information is not present for this site pin, returns None.
If this is an output pin, returns OutPinTiming.
If this is an input pin, returns InPinTiming.
"""
if isinstance(site_pin_info, str):
return site_pin_info, None
wire = site_pin_info['wire']
if 'delay' not in site_pin_info:
return None
delays = fast_slow_tuple_to_corners(site_pin_info['delay'])
if 'cap' in site_pin_info:
assert 'res' not in site_pin_info
return wire, InPinTiming(
delays=delays,
capacitance=float(site_pin_info['cap']) / CAPACITANCE_FACTOR,
)
else:
assert 'res' in site_pin_info
return wire, OutPinTiming(
delays=delays,
drive_resistance=float(site_pin_info['res']) / RESISTANCE_FACTOR,
)
def get_wires(wires):
""" Converts database input to dictionary of tile wires to wire timing.
Returns dictionary of tile wire name to RcElement or None. """
if isinstance(wires, list):
# Handle old database gracefully.
return {wire: None for wire in wires}
output = {}
for wire, rc_json in wires.items():
if rc_json is None:
output[wire] = RcElement(
resistance=0,
capacitance=0,
)
else:
output[wire] = RcElement(
resistance=float(rc_json['res']) / RESISTANCE_FACTOR,
capacitance=float(rc_json['cap']) / CAPACITANCE_FACTOR,
)
return output
def is_pass_transistor(pip_json):
""" Returns boolean if pip JSON indicates pip is a pass transistor.
Always returns False if database lacks this information.
"""
if 'is_pass_transistor' in pip_json:
return bool(int(pip_json['is_pass_transistor']))
else:
return False
class Tile(object):
""" Provides abstration of a tile in the database. """
@ -49,17 +262,32 @@ class Tile(object):
def yield_sites(sites):
for site in sites:
site_pins = []
for name, site_pin_info in site['site_pins'].items():
if site_pin_info is not None:
wire, timing = get_site_pin_timing(site_pin_info)
site_pins.append(
SitePin(
name=name,
wire=wire,
timing=timing,
))
else:
site_pins.append(
SitePin(
name=name,
wire=None,
timing=None,
))
yield Site(
name=site['name'],
prefix=site['prefix'],
type=site['type'],
x=site['x_coord'],
y=site['y_coord'],
site_pins=tuple(
SitePin(
name=name,
wire=wire,
) for name, wire in site['site_pins'].items()))
site_pins=site_pins,
)
def yield_pips(pips):
for name, pip in pips.items():
@ -70,12 +298,15 @@ class Tile(object):
can_invert=bool(int(pip['can_invert'])),
is_directional=bool(int(pip['is_directional'])),
is_pseudo=bool(int(pip['is_pseudo'])),
is_pass_transistor=is_pass_transistor(pip),
timing=get_pip_timing(pip.get('src_to_dst')),
backward_timing=get_pip_timing(pip.get('dst_to_src')),
)
with open(self.tile_dbs.tile_type) as f:
tile_type = json.load(f)
assert self.tilename_upper == tile_type['tile_type']
self.wires = tile_type['wires']
self.wires = get_wires(tile_type['wires'])
self.sites = tuple(yield_sites(tile_type['sites']))
self.pips = tuple(yield_pips(tile_type['pips']))

550
prjxray/timing.py Normal file
View File

@ -0,0 +1,550 @@
""" 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"
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
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 {
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