import logging
logger = logging.getLogger('jasper.toolflow.constraints')
[docs]class PortConstraint(object):
"""
A class to facilitate constructing abstracted port constraints.
Eg, adc_data[7:0] <=> zdok0[7:0]
which can later be translated into physical constraints by providing
information about a target platform.
This assigns the port LOC and voltage constraints to user_const.xdc, for example:
``PortConstraint('A', 'A')`` is translated to ``set_property PACKAGE_PIN BC27 [get_ports A]`` and
``set_property IOSTANDARD LVCMOS18 [A]`` in the xdc file. The
"BC27" LOC and "LVCMOS18" is determined by the platform yaml file, which contains all the platform top level
ports and LOC assignments.
"""
[docs] def __init__(self, portname, iogroup, port_index=[], iogroup_index=[0], loc=None, iostd=None, drive_strength=None, diff_term=None):
"""
Construct a PortConstraint instance.
:param portname: The name (in verilog) of the port
:type portname: str
:param port_index: Specify an offset of the port index to attach to iogroup[index]. This feature was added so that
we can do (eg.) myport[3:0] <=> gpioA[3:0], myport[7:4] <=> gpioB[3:0]
:type port_index: int
:param iogroup: The abstract name of the ports physical connection (eg. zdok0, zdok1, gpioa)
:type iogroup: str
:param iogroup_index: The index of the abstract name to which the HDL port should connect
:type iogroup_index: int or list
:param loc: Specify a loc to construct a physical constraint, forgoing the abstract names. Experimental.
:type loc: list
:param iostd: Specify an iostd to construct a physical constraint, forgoing the abstract names. Experimental.
:type loc: list
:param drive_strength: Specify a drive strength to construct a physical constraint, forgoing the abstract names. Experimental.
:type loc: list of ints
:param diff_term: specify the use of internal 100 ohm termination for lvds pins. set to `TERM_100` or `TERM_NONE` (default). Experimental.
:type diff_term: list of str
"""
logger.debug('new PortConstraint:')
logger.debug(' portname: %s'%portname)
logger.debug(' iogroup: %s'%iogroup)
logger.debug(' port_index: %s'%port_index)
logger.debug(' iogroup_index: %s'%iogroup_index)
logger.debug(' loc: %s'%loc)
logger.debug(' iostd: %s'%iostd)
logger.debug(' drive_strength: %s'%drive_strength)
logger.debug(' diff_term: %s'%diff_term)
if port_index == []:
self.is_vector = False
else:
self.is_vector = True
# In Python3 range(x,y) returns a range instance, not a list.
# Deal with that here so the user doesn't have to
if isinstance(port_index, range): port_index = list(port_index)
if isinstance(iogroup_index, range): iogroup_index = list(iogroup_index)
if isinstance(loc, range): loc = list(loc)
if isinstance(iostd, range): iostd = list(iostd)
if isinstance(drive_strength, range): drive_strength = list(drive_strength)
if isinstance(diff_term, range): diff_term = list(diff_term)
if type(port_index) != list: port_index = [port_index]
if type(iogroup_index) != list: iogroup_index = [iogroup_index]
if type(loc) != list: loc = [loc]
if type(iostd) != list: iostd = [iostd]
if type(drive_strength) != list: drive_strength = [drive_strength]
if type(diff_term) != list: diff_term = [diff_term]
self.portname = portname.strip(' ') #clear out whitespace
self.port_index = port_index
self.iogroup = iogroup.strip(' ')
self.iogroup_index = iogroup_index
self.loc = loc
self.iostd = iostd
self.drive_strength = drive_strength
self.width = len(iogroup_index)
self.diff_term = diff_term
if (port_index != []) and (len(port_index) != len(iogroup_index)):
raise ValueError("Tried to constrain a multidimensional signal with iogroup with different dimensions!")
if self.loc == [None]:
self.loc *= self.width
elif len(self.loc) != self.width:
raise ValueError("Tried to constrain a multidimensional signal with a list of LOCs with different dimensions!")
if len(self.iostd) == 1:
self.iostd *= self.width
elif len(self.iostd) != self.width:
raise ValueError("Tried to constrain a multidimensional signal with a list of IOSTDs with different dimensions!")
if len(self.drive_strength) == 1:
self.drive_strength *= self.width
elif len(self.drive_strength) != self.width:
raise ValueError("Tried to constrain a multidimensional signal with a list of drive strengths with different dimensions!")
if len(self.diff_term) == 1:
self.diff_term *= self.width
elif len(self.diff_term) != self.width:
raise ValueError("Tried to constrain a multidimensional signal with a list of differential terminations with different dimensions!")
def __str__(self):
"""
A user friendly string representation
"""
return self.portname
[docs] def gen_physical_const(self, platform):
"""
Set the LOC and IOSTDs of an abstract constraint for a given platform.
:param platform: The platform instance against which to evaluate the constraint(s).
:type platform: Platform
"""
logger.debug('Attempting to get physical constraints for port %s on %s '%(self.portname, self.iogroup))
pins = platform.get_pins(self.iogroup, index=self.iogroup_index)
for i in range(self.width):
if self.loc[i] is None:
logger.debug('Setting pin loc: %s %s'%(self.portname, pins[i].loc))
self.loc[i] = pins[i].loc
if self.iostd[i] is None:
logger.debug('Setting pin iostd: %s %s'%(self.portname, pins[i].iostd))
self.iostd[i] = pins[i].iostd
if self.drive_strength[i] is None:
logger.debug('Setting pin drive_strength: %s %s'%(self.portname, pins[i].drive_strength))
self.drive_strength[i] = pins[i].drive_strength
[docs]class ClockConstraint(object):
"""
A clock constraint -- simply holds the name of the clock
signal, clock name, whether clock source is get_ports or get_pins, whether a virtual clock, waveform parameters for
duty cycle and the corresponding clock freq and period.
This assigns the clock timing constraint on the clock port in user_const.xdc, for example:
``ClockConstraint('A','A', period=6.4, port_en=True, virtual_en=False, waveform_min=0.0, waveform_max=3.2))``
is translated to ``create_clock -period 6.400 -name A -waveform {0.000 3.200} [get_ports {A}]`` in the xdc file.
This tells Vivado which ports should be clocks.
"""
[docs] def __init__(self, signal=None, name=None, freq=None, period=None, port_en=True, virtual_en=False, waveform_min=0., waveform_max=None):
"""
Construct a ClockConstraint instance.
:param signal: The signal name of the clock port
:type signal: str
:param name: The name of the clock
:type name: str
:param freq: The clock frequency in MHz (no need to specify period if the frequency is specified)
:type freq: float
:param period: The period of the clock in ns (no need to specify frequency if the period is specified)
:type period: float
:param port_en: If True then the clock port is enabled. If False then the clock port is bypassed for the case of
a virtual clock.
:type port_en: boolean
:param virtual_en: This is set to True when using a virtual clock, otherwise it is False.
:type virtual_en: bool
:param waveform_min: This parameter is used to determine the duty cycle of the clock in ns. Typically 0ns.
:type waveform_min: float
:param waveform_max: This parameter is used to determine the duty cycle of the clock in ns. Typically half the
period of the clock for a 50% duty cycle.
:type waveform_max: float
"""
logger.debug('New clock constraint')
logger.debug('clock signal: %s'%signal)
logger.debug('name: %s'%name)
logger.debug('freq: %s'%freq)
logger.debug('period: %s'%period)
logger.debug('port_en: %s'%port_en)
logger.debug('waveform_min: %s'%waveform_min)
logger.debug('waveform_max: %s'%waveform_max)
logger.debug('virtual_en: %s' % virtual_en)
self.signal = signal
self.name = name or signal + '_CLK'
if not (bool(freq) ^ bool(period)):
raise ValueError('Enter one of either freq or period')
self.freq = float(freq or 1000./period)
self.period = float(period or 1000./freq)
self.port_en = port_en
self.waveform_min = float(waveform_min)
if waveform_max is not None:
self.waveform_max = float(waveform_max)
else:
self.waveform_max = self.period / 2.
self.virtual_en = virtual_en
[docs]class GenClockConstraint(object):
"""
A clock generation constraint -- simply holds the name of the clock
signal, clock name, clock source and divide by value.
This assigns the generated clock timing constraint on a non global clock port in user_const.xdc, for example:
``GenClockConstraint(signal='sub/Q', name='sub/CLK', divide_by=16, clock_source='sub/C')``
is translated to ``create_generated_clock -name sub/CLK -source [get_pins {sub/C}] -divide_by 16
[get_pins {sub/Q}]`` in the xdc file.
This constraint is used to assign a clock to signals that are not inferred by Vivado naturally and should be.
"""
[docs] def __init__(self, signal, name=None, divide_by=None, clock_source=None):
"""
Construct a GenClockConstraint instance.
:param signal: The signal name that is required to be a clock
:type signal: str
:param name: The name of the generated clock
:type name: str
:param divide_by: The value to divide the clock_source by in order to determine the clock frequency of the
generated clock in MHz
:type divide_by: int
:param clock_source: This is the clock source (input) of the generated clock. The clock_source and the
divide_by value determined the generated clock out frequency in MHz:
generated clock in MHz = clock_source*divide_by
:type clock_source: str
"""
logger.debug('New Generated clock constraint')
logger.debug('clock signal: %s'%signal)
logger.debug('name: %s'%name)
logger.debug('divide_by: %s'%divide_by)
logger.debug('clock source: %s'%clock_source)
self.signal = signal
self.name = name or signal + '_CLK'
self.divide_by = int(divide_by)
self.clock_source = clock_source
[docs]class ClockGroupConstraint(object):
"""
A clock group constraint -- simply holds the name of both clock domains and the domain relationship e.g. asynchronous
This assigns the clock group timing constraint on two or more clock groups in user_const.xdc, for example:
``ClockGroupConstraint('A', 'B', 'asynchronous')`` is translated to
``set_clock_groups -asynchronous -group [get_clocks A] -group [get_clocks B]`` in the xdc file.
This constraint is used to cut the clock relationship between two or more clock groups.
"""
[docs] def __init__(self, clock_name_group_1=None, clock_name_group_2=None, clock_domain_relationship=None):
"""
Construct a ClockGroupConstraint instance.
:param clock_name_group_1: The clock name of the first group e.g. the clock port name or virtual clock name
:type clock_name_group_1: str
:param clock_name_group_2: The clock name of the second group e.g. the clock port name or virtual clock name
:type clock_name_group_2: str
:param clock_domain_relationship: This specifies the relationship between the two clock name groups. Typically
this is set to ``asynchronous`` which tells the Vivado timing analyzer to ignore the timing relationship
between these two clock domains, as the clocks are asynchronous.
:type clock_domain_relationship: str
"""
logger.debug('New clock group constraint')
logger.debug('clock name group 1: %s'%clock_name_group_1)
logger.debug('clock name group 2: %s'%clock_name_group_2)
logger.debug('clock domain relationship: %s'%clock_domain_relationship)
self.clock_name_group_1 = clock_name_group_1
self.clock_name_group_2 = clock_name_group_2
self.clock_domain_relationship = clock_domain_relationship
[docs]class OutputDelayConstraint(object):
"""
An output delay constraint - simply holds the name of the reference clock, constraint type (min or max), constraint
delay value (ns), whether an existing constraint exists and a new one needs to be added and the port name that the
constraint applies to.
This assigns the clock output delay timing constraint in user_const.xdc, for example:
``OutputDelayConstraint(clkname='A', consttype='min', constdelay_ns=1.0, add_delay_en=True, portname='B')`` is
translated to
``set_output_delay -clock [get_clocks A] -min -add_delay 1.000 [get_ports {B}]`` in the xdc file.
This constraint is used to assign output constraints referenced to the clock.
"""
[docs] def __init__(self, clkname=None, consttype=None, constdelay_ns=None, add_delay_en=None, portname=None ):
"""
Construct a OutputDelayConstraint instance.
:param clkname: The clock name which the port name is referenced to
:type clkname: str
:param consttype: This is constraint type: either be a ``min`` (hold) or ``max`` (setup).
:type consttype: str
:param constdelay_ns: This is the constraint delay in ns - takes into account the Tsu, Th, clock skew and board
delay.
:type constdelay_ns: float
:param add_delay_en: If more than one constraint is needed on the portname then this is True, else set it to
False.
:type add_delay_en: bool
:param portname: The port name of the signal that needs to be constrained.
:type portname: str
"""
logger.debug('New output delay constraint')
logger.debug('clock name: %s'%clkname)
logger.debug('constraint type: %s'%consttype)
logger.debug('constraint delay: %s'%constdelay_ns)
logger.debug('add delay enabled: %s'%add_delay_en)
logger.debug('port name: %s'%portname)
self.clkname = clkname
self.consttype = consttype
self.constdelay_ns = constdelay_ns
self.add_delay_en = add_delay_en
self.portname = portname
[docs]class MaxDelayConstraint(object):
"""
A set max delay constraint - simply holds the source, destination paths and the constraint
delay value (ns).
This assigns the max delay timing constraint in user_const.xdc, for example:
``MaxDelayConstraint(destpath='[get_ports {A}]', constdelay_ns=1.0)`` is translated to
``set_max_delay 1.0 -to [get_ports {A}]`` in the xdc file.
This constraint is used when there is no clock reference.
"""
[docs] def __init__(self, sourcepath=None, destpath=None , constdelay_ns=None):
"""
Construct a MaxDelayConstraint instance.
:param sourcepath: The source path that the constraint is applied to - includes path and port names.
:type sourcepath: str
:param destpath: The destination path that the constraint is applied to - includes path and port names.
:type destpath: str
:param constdelay_ns: This is the constraint delay in ns - takes into account the Tsu, clock skew and board
delay.
:type constdelay_ns: float
"""
logger.debug('New set max delay constraint')
logger.debug('source path: %s'%sourcepath)
logger.debug('destination path: %s'%destpath)
logger.debug('constraint delay: %s'%constdelay_ns)
self.sourcepath = sourcepath
self.destpath = destpath
self.constdelay_ns = constdelay_ns
[docs]class MinDelayConstraint(object):
"""
A set min delay constraint - simply holds the source, destination paths and the constraint
delay value (ns).
This assigns the min delay timing constraint in user_const.xdc, for example:
``MinDelayConstraint(destpath='[get_ports {A}]', constdelay_ns=1.0)`` is translated to
``set_min_delay 1.0 -to [get_ports {A}]`` in the xdc file.
This constraint is used when there is no clock reference.
"""
[docs] def __init__(self, sourcepath=None, destpath=None , constdelay_ns=None):
"""
Construct a MinDelayConstraint instance.
:param sourcepath: The source path that the constraint is applied to - includes path and port names.
:type sourcepath: str
:param destpath: The destination path that the constraint is applied to - includes path and port names.
:type destpath: str
:param constdelay_ns: This is the constraint delay in ns - takes into account the Th, clock skew and board
delay.
:type constdelay_ns: float
"""
logger.debug('New set max delay constraint')
logger.debug('source path: %s'%sourcepath)
logger.debug('destination path: %s'%destpath)
logger.debug('constraint delay: %s'%constdelay_ns)
self.sourcepath = sourcepath
self.destpath = destpath
self.constdelay_ns = constdelay_ns
[docs]class FalsePathConstraint(object):
"""
A false path constraint - simply holds the source and destination paths.
This assigns the false path timing constraint in user_const.xdc, for example:
``FalsePathConstraint(destpath='[get_ports {A}]')`` is translated to
``set_false_path -to [get_ports {A}]`` in the xdc file.
Any path that appears in the FalsePathConstraint is ignored by the Vivado timing analyzer.
"""
[docs] def __init__(self, sourcepath=None, destpath=None):
"""
Construct a FalsePathConstraint instance.
:param sourcepath: The source path that the constraint is applied to - includes path and port names.
:type sourcepath: str
:param destpath: The destination path that the constraint is applied to - includes path and port names.
:type destpath: str
"""
logger.debug('New false path constraint')
logger.debug('source path: %s'%sourcepath)
logger.debug('destination path: %s'%destpath)
self.sourcepath = sourcepath
self.destpath = destpath
[docs]class MultiCycleConstraint(object):
"""
A multi cycle constraint - simply holds the multi cycle type (steup or hold), source, destination paths and
multi cycle delay value in clock cycles.
This assigns the multicycle timing constraint in user_const.xdc, for example:
``MultiCycleConstraint(multicycletype='setup',sourcepath='get_clocks B', destpath='get_ports A', multicycledelay=4)``
is translated to ``set_multicycle_path -setup -from [get_ports A] -to [get_clocks B] 4`` in the xdc file.
This tells the Vivado timing analyzer that the signal will take more than one clock cycle to propagate through the logic.
"""
[docs] def __init__(self, multicycletype=None, sourcepath=None, destpath=None, multicycledelay=None):
"""
Construct a MultiCycleConstraint instance.
:param multicycletype: The type of multicycle constraint: either ``setup`` or ``hold``
:type multicycletype: str
:param sourcepath: The source path that the constraint is applied to - includes path and port names.
:type sourcepath: str
:param destpath: The destination path that the constraint is applied to - includes path and port names.
:type destpath: str
:param multicycledelay: This represents the number of clock cycles to delay.
:type multicycledelay: int
"""
logger.debug('New Multi Cycle constraint')
logger.debug('Multi cycle type: %s'%multicycletype)
logger.debug('source path: %s'%sourcepath)
logger.debug('destination path: %s'%destpath)
logger.debug('multi cycle delay: %d' % multicycledelay)
self.multicycletype = multicycletype
self.sourcepath = sourcepath
self.destpath = destpath
self.multicycledelay = multicycledelay
[docs]class RawConstraint(object):
"""
A class for raw constraints -- strings to be dumper unadulterated into a
constraint file.
This assigns any raw constraints (set_property, pblock etc) in user_const.xdc, for example:
``RawConstraint('set_property OFFCHIP_TERM NONE [get_ports A]')``
is translated to ``set_property OFFCHIP_TERM NONE [get_ports A]`` in the xdc file.
Any constraint not handled in the above classes can be added using the raw constraints.
"""
[docs] def __init__(self, const):
"""
Construct a RawConstraint instance.
:param const: This represents the path with port names that the constraint is applied to
:type const: str
"""
if const.endswith('\n'):
self.raw = const
else:
self.raw = const + '\n'