diff --git a/prjxray/math_models.py b/prjxray/math_models.py new file mode 100644 index 00000000..a649302c --- /dev/null +++ b/prjxray/math_models.py @@ -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 diff --git a/prjxray/tile.py b/prjxray/tile.py index d6f1ae6d..4ca5a702 100644 --- a/prjxray/tile.py +++ b/prjxray/tile.py @@ -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'])) diff --git a/prjxray/timing.py b/prjxray/timing.py new file mode 100644 index 00000000..29a3dd78 --- /dev/null +++ b/prjxray/timing.py @@ -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