import abc
import more_itertools as itertools
import itertools as plain_itertools
import re
import logging
from . import types
from .types import BaseModel, Union, Optional, Literal, List, set_context
from pydantic import Field
logger = logging.getLogger(__name__)
pattern = re.compile(r'(?<!^)(?=[A-Z])')
[docs]def get_instances_from_hacked_dataclasses(constraint):
assert constraint.parent.parent is not None, 'Cannot access parent scope'
if hasattr(constraint.parent.parent, 'graph'):
instances = {k for k, v in constraint.parent.parent.graph.nodes.items() if v['inst_type'] != 'net'}
elif hasattr(constraint.parent.parent, 'elements'):
instances = {x.name for x in constraint.parent.parent.elements}
elif hasattr(constraint.parent.parent, 'instances'):
instances = {x.instance_name for x in constraint.parent.parent.instances}
else:
raise NotImplementedError(f"Cannot handle {type(constraint.parent.parent)}")
names = {x.name for x in constraint.parent if hasattr(x, 'name')}
return set.union(instances, names)
[docs]def validate_instances(cls, value):
# instances = cls._validator_ctx().parent.parent.instances
instances = get_instances_from_hacked_dataclasses(cls._validator_ctx())
assert isinstance(instances, set), 'Could not retrieve instances from subcircuit definition'
for x in value: # explicit loop to point out the not found instance
assert x in instances or x.upper() in instances, f"Instance {x} not found in the circuit"
return [x.upper() for x in value]
[docs]def upper_case(cls, value):
return [v.upper() for v in value]
[docs]def assert_non_negative(cls, value):
assert value >= 0, f'Value must be non-negative: {value}'
return value
[docs]class SoftConstraint(types.BaseModel):
constraint: str
def __init__(self, *args, **kwargs):
constraint = pattern.sub(
'_', self.__class__.__name__).lower()
if 'constraint' not in kwargs or kwargs['constraint'] == self.__class__.__name__:
kwargs['constraint'] = constraint
else:
assert constraint == kwargs[
'constraint'], f'Unexpected `constraint` {kwargs["constraint"]} (expected {constraint})'
super().__init__(*args, **kwargs)
[docs]class HardConstraint(SoftConstraint, abc.ABC):
[docs] @abc.abstractmethod
def translate(self, solver):
'''
Abstract Method for built in self-checks
Every class that inherits from HardConstraint
MUST implement this function.
Function must yield a list of mathematical
expressions supported by the 'solver'
backend. This can be done using multiple
'yield' statements or returning an iterable
object such as list
'''
pass
[docs]class UserConstraint(HardConstraint, abc.ABC):
[docs] @abc.abstractmethod
def yield_constraints(self):
'''
Abstract Method to yield low level constraints
Every class that inherits from UserConstraint
MUST implement this function. This ensures
clean separation of user-facing constraints
from PnR constraints
'''
pass
[docs] def translate(self, solver):
for constraint in self.yield_constraints():
if isinstance(constraint, HardConstraint):
yield from constraint.translate(solver)
[docs]class Order(HardConstraint):
'''
Defines a placement order for instances in a subcircuit.
Args:
instances (list[str]): List of :obj:`instances`
direction (str, optional): The following options for direction are supported
:obj:`'horizontal'`, placement order is left to right or vice-versa.
:obj:`'vertical'`, placement order is bottom to top or vice-versa.
:obj:`'left_to_right'`, placement order is left to right.
:obj:`'right_to_left'`, placement order is right to left.
:obj:`'bottom_to_top'`, placement order is bottom to top.
:obj:`'top_to_bottom'`, placement order is top to bottom.
:obj:`None`: default (:obj:`'horizontal'` or :obj:`'vertical'`)
abut (bool, optional): If `abut` is `true` adjoining instances will touch
.. image:: ../images/OrderBlocks.PNG
:align: center
WARNING: `Order` does not imply aligment / overlap
of any sort (See `Align`)
Example: ::
{"constraint":"Order", "instances": ['MN0', 'MN1', 'MN2'], "direction": "left_to_right"}
'''
instances: List[str]
direction: Optional[Literal[
'horizontal', 'vertical',
'left_to_right', 'right_to_left',
'bottom_to_top', 'top_to_bottom'
]]
abut: bool = False
[docs] @types.validator('instances', allow_reuse=True)
def order_instances_validator(cls, value):
assert len(value) >= 2, 'Must contain at least two instances'
return validate_instances(cls, value)
[docs] def translate(self, solver):
def cc(b1, b2, c='x'): # Create coordinate constraint
if self.abut:
return getattr(b1, f'ur{c}') == getattr(b2, f'll{c}')
else:
return getattr(b1, f'ur{c}') <= getattr(b2, f'll{c}')
bvars = solver.iter_bbox_vars(self.instances)
for b1, b2 in itertools.pairwise(bvars):
if self.direction == 'left_to_right':
yield cc(b1, b2, 'x')
elif self.direction == 'right_to_left':
yield cc(b2, b1, 'x')
elif self.direction == 'bottom_to_top':
yield cc(b1, b2, 'y')
elif self.direction == 'top_to_bottom':
yield cc(b2, b1, 'y')
elif self.direction == 'horizontal':
yield solver.Or(
cc(b1, b2, 'x'),
cc(b2, b1, 'x'))
elif self.direction == 'vertical':
yield solver.Or(
cc(b1, b2, 'y'),
cc(b2, b1, 'y'))
else:
yield solver.Or(
cc(b1, b2, 'x'),
cc(b2, b1, 'x'),
cc(b1, b2, 'y'),
cc(b2, b1, 'y'))
[docs]class Align(HardConstraint):
'''
`Instances` will be aligned along `line`. Could be
strict or relaxed depending on value of `line`
Args:
instances (list[str]): List of `instances`
line (str, optional): The following `line` values are currently supported:
:obj:`h_any`, align instance's top, bottom or anything in between.
:obj:`'v_any'`, align instance's left, right or anything in between.
:obj:`'h_top'`, align instance's horizontally based on top.
:obj:`'h_bottom'`, align instance's horizomtally based on bottom.
:obj:`'h_center'`, align instance's horizontally based on center.
:obj:`'v_left'`, align instance's vertically based on left.
:obj:`'v_right'`, align instance's vertically based on right.
:obj:`'v_center'`, align instance's vertically based on center.
:obj:`None`:default (:obj:`'h_any'` or :obj:`'v_any'`).
.. image:: ../images/AlignBlocks.PNG
:align: center
WARNING: `Align` does not imply ordering of any sort
(See `Order`)
Example: ::
{"constraint":"Align", "instances": ['MN0', 'MN1', 'MN2'], "line": "v_center"}
'''
instances: List[str]
line: Optional[Literal[
'h_any', 'h_top', 'h_bottom', 'h_center',
'v_any', 'v_left', 'v_right', 'v_center'
]]
_inst_validator = types.validator('instances', allow_reuse=True)(validate_instances)
[docs] @types.validator('instances', allow_reuse=True)
def align_instances_validator(cls, value):
assert len(value) >= 2, 'Must contain at least two instances'
return validate_instances(cls, value)
[docs] def translate(self, solver):
bvars = solver.iter_bbox_vars(self.instances)
for b1, b2 in itertools.pairwise(bvars):
if self.line == 'h_top':
yield b1.ury == b2.ury
elif self.line == 'h_bottom':
yield b1.lly == b2.lly
elif self.line == 'h_center':
yield (b1.lly + b1.ury) / 2 == (b2.lly + b2.ury) / 2
elif self.line == 'h_any':
yield solver.Or( # We don't know which bbox is higher yet
solver.And(b1.lly >= b2.lly, b1.ury <= b2.ury),
solver.And(b2.lly >= b1.lly, b2.ury <= b1.ury)
)
elif self.line == 'v_left':
yield b1.llx == b2.llx
elif self.line == 'v_right':
yield b1.urx == b2.urx
elif self.line == 'v_center':
yield (b1.llx + b1.urx) / 2 == (b2.llx + b2.urx) / 2
elif self.line == 'v_any':
yield solver.Or( # We don't know which bbox is wider yet
solver.And(b1.urx <= b2.urx, b1.llx >= b2.llx),
solver.And(b2.urx <= b1.urx, b2.llx >= b1.llx)
)
else:
yield solver.Or( # h_any OR v_any
solver.And(b1.urx <= b2.urx, b1.llx >= b2.llx),
solver.And(b2.urx <= b1.urx, b2.llx >= b1.llx),
solver.And(b1.lly >= b2.lly, b1.ury <= b2.ury),
solver.And(b2.lly >= b1.lly, b2.ury <= b1.ury)
)
[docs]class Enclose(HardConstraint):
'''
Enclose `instances` within a flexible bounding box
with `min_` & `max_` bounds
Args:
instances (list[str], optional): List of `instances`
min_height (int, optional): assign minimum height to the subcircuit
max_height (int, optional): assign maximum height to the subcircuit
min_width (int, optional): assign minimum width to the subcircuit
max_width (int, optional): assign maximum width to the subcircuit
min_aspect_ratio (float, optional): assign minimum aspect ratio to the subcircuit
max_aspect_ratio (float, optional): assign maximum aspect ratio to the subcircuit
Note: Specifying any one of the following variables
makes it a valid constraint but you may wish to
specify more than one for practical purposes
Example: ::
{"constraint":"Enclose", "instances": ['MN0', 'MN1', 'MN2'], "min_aspect_ratio": 0.1, "max_aspect_ratio": 10 }
'''
instances: Optional[List[str]]
min_height: Optional[int]
max_height: Optional[int]
min_width: Optional[int]
max_width: Optional[int]
min_aspect_ratio: Optional[float]
max_aspect_ratio: Optional[float]
_inst_validator = types.validator('instances', allow_reuse=True)(validate_instances)
[docs] @types.validator('max_aspect_ratio', allow_reuse=True)
def bound_in_box_optional_fields(cls, value, values):
assert value or any(
getattr(values, x, None)
for x in (
'min_height',
'max_height',
'min_width',
'max_width',
'min_aspect_ratio'
)
), 'Too many optional fields'
return value
[docs] def translate(self, solver):
bb = solver.bbox_vars(solver.label(self))
if self.min_width:
yield bb.urx - bb.llx >= self.min_width
if self.min_height:
yield bb.ury - bb.lly >= self.min_height
if self.max_width:
yield bb.urx - bb.llx <= self.max_width
if self.max_height:
yield bb.ury - bb.lly <= self.max_height
if self.min_aspect_ratio:
yield solver.cast(
(bb.ury - bb.lly) / (bb.urx - bb.llx),
float) >= self.min_aspect_ratio
if self.max_aspect_ratio:
yield solver.cast(
(bb.ury - bb.lly) / (bb.urx - bb.llx),
float) <= self.max_aspect_ratio
bvars = solver.iter_bbox_vars(self.instances)
for b in bvars:
yield b.urx <= bb.urx
yield b.llx >= bb.llx
yield b.ury <= bb.ury
yield b.lly >= bb.lly
[docs]class Spread(HardConstraint):
'''
Spread `instances` by forcing minimum spacing along
`direction` if two instances overlap in other direction
Args:
instances (list[str]): List of `instances`
direction (str, optional): Direction for placement spread.
(:obj:`'horizontal'` or :obj:`'vertical'` or :obj:`None`)
distance (int): Distance in nanometer
WARNING: This constraint checks for overlap but
doesn't enforce it (See `Align`)
Example: ::
{
"constraint": "Spread",
"instances": ['MN0', 'MN1', 'MN2'],
"direction": horizontal,
"distance": 100
}
'''
instances: List[str]
direction: Optional[Literal['horizontal', 'vertical']]
distance: int # in nm
[docs] @types.validator('instances', allow_reuse=True)
def spread_instances_validator(cls, value):
assert len(value) >= 2, 'Must contain at least two instances'
return validate_instances(cls, value)
[docs] def translate(self, solver):
def cc(b1, b2, c='x'):
d = 'y' if c == 'x' else 'x'
return solver.Implies(
solver.And( # overlap orthogonal to c
getattr(b1, f'ur{d}') > getattr(b2, f'll{d}'),
getattr(b2, f'ur{d}') > getattr(b1, f'll{d}'),
),
solver.Abs( # distance in c coords
(
getattr(b1, f'll{c}')
+ getattr(b1, f'ur{c}')
) - (
getattr(b2, f'll{c}')
+ getattr(b2, f'ur{c}')
)
) >= self.distance * 2
)
bvars = solver.iter_bbox_vars(self.instances)
for b1, b2 in itertools.pairwise(bvars):
if self.direction == 'horizontal':
yield cc(b1, b2, 'x')
elif self.direction == 'vertical':
yield cc(b1, b2, 'y')
else:
yield solver.Or(
cc(b1, b2, 'x'),
cc(b1, b2, 'y')
)
[docs]class AssignBboxVariables(HardConstraint):
bbox_name: str
llx: int
lly: int
urx: int
ury: int
[docs] @types.validator('urx', allow_reuse=True)
def x_is_valid(cls, value, values):
assert value > values['llx'], 'Reflection is not supported yet'
return value
[docs] @types.validator('ury', allow_reuse=True)
def y_is_valid(cls, value, values):
assert value > values['lly'], 'Reflection is not supported yet'
return value
[docs] def translate(self, solver):
bvar = solver.bbox_vars(self.bbox_name)
yield bvar.llx == self.llx
yield bvar.lly == self.lly
yield bvar.urx == self.urx
yield bvar.ury == self.ury
[docs]class AspectRatio(HardConstraint):
"""
Define lower and upper bounds on aspect ratio (=width/height) of a subcircuit
`ratio_low` <= width/height <= `ratio_high`
Args:
subcircuit (str) : Name of subciruit
ratio_low (float): Minimum aspect ratio (default 0.1)
ratio_high (float): Maximum aspect ratio (default 10)
weight (int): Weigth of this constraint (default 1)
Example: ::
{"constraint": "AspectRatio", "ratio_low": 0.1, "ratio_high": 10, "weight": 1 }
"""
subcircuit: str
ratio_low: float = 0.1
ratio_high: float = 10
weight: int = 1
_ratio_low_validator = types.validator('ratio_low', allow_reuse=True)(assert_non_negative)
[docs] @types.validator('ratio_high', allow_reuse=True)
def ratio_high_validator(cls, value, values):
assert value > values['ratio_low'], f'AspectRatio:ratio_high {value} should be greater than ratio_low {values["ratio_low"]}'
return value
[docs] def translate(self, solver):
bvar = solver.bbox_vars('subcircuit')
yield solver.cast(bvar.urx-bvar.llx, float) >= self.ratio_low * solver.cast(bvar.ury-bvar.lly, float)
yield solver.cast(bvar.urx-bvar.llx, float) < self.ratio_high * solver.cast(bvar.ury-bvar.lly, float)
[docs]class Boundary(HardConstraint):
"""
Define `max_height` and/or `max_width` on a subcircuit in micrometers.
Args:
subcircuit (str) : Name of subcircuit
max_width (float, Optional) = 10000
max_height (float, Optional) = 10000
Example: ::
{"constraint": "Boundary", "subcircuit": "OTA", "max_height": 100 }
"""
subcircuit: str
max_width: Optional[float] = 10000
max_height: Optional[float] = 10000
_max_width = types.validator('max_width', allow_reuse=True)(assert_non_negative)
_max_height = types.validator('max_height', allow_reuse=True)(assert_non_negative)
[docs] def translate(self, solver):
bvar = solver.bbox_vars('subcircuit')
if self.max_width is not None:
yield solver.cast(bvar.urx-bvar.llx, float) <= 1000*self.max_width # in nanometer
if self.max_height is not None:
yield solver.cast(bvar.ury-bvar.lly, float) <= 1000*self.max_height # in nanometer
[docs]class GroupBlocks(HardConstraint):
"""GroupBlocks
Forces a hierarchy creation for group of instances.
This brings the instances closer.
This reduces the problem statement for placer thus providing
better solutions.
Args:
instances (list[str]): List of :obj:`instances`
name (str): alias for the list of :obj:`instances`
generator (dict): adds a generator constraint to the created groupblock, look into the generator constraint for more options
Example: ::
{
"constraint":"GroupBlocks",
"name": "group1",
"instances": ["MN0", "MN1", "MN3"]
"generator": {name: 'MOS',
'parameters':
{
"pattern": "cc",
}
}
}
"""
name: str
instances: List[str]
generator: Optional[dict]
[docs] @types.validator('name', allow_reuse=True)
def group_block_name(cls, value):
assert value, 'Cannot be an empty string'
return value.upper()
[docs] def translate(self, solver):
# Non-zero width / height
bb = solver.bbox_vars(self.name)
yield bb.llx < bb.urx
yield bb.lly < bb.ury
# Grouping into common bbox
for b in solver.iter_bbox_vars(self.instances):
yield b.urx <= bb.urx
yield b.llx >= bb.llx
yield b.ury <= bb.ury
yield b.lly >= bb.lly
instances = get_instances_from_hacked_dataclasses(self)
for b in solver.iter_bbox_vars((x for x in instances if x not in self.instances)):
yield solver.Or(
b.urx <= bb.llx,
bb.urx <= b.llx,
b.ury <= bb.lly,
bb.ury <= b.lly,
)
# You may chain constraints together for more complex constraints by
# 1) Assigning default values to certain attributes
# 2) Using custom validators to modify attribute values
# Note: Do not implement translate() here as it may be ignored
# by certain engines
[docs]class AlignInOrder(UserConstraint):
'''
Align `instances` on `line` ordered along `direction`
Args:
instances (list[str]): List of :obj:`instances`
line (str, optional): The following `line` values are currently supported:
:obj:`'top'`, align instance's horizontally based on top.
:obj:`'bottom'`, align instance's horizomtally based on bottom.
:obj:`'center'`, align instance's horizontally based on center.
:obj:`'left'`, align instance's vertically based on left.
:obj:`'right'`, align instance's vertically based on right.
direction: The following `direction` values are supported:
:obj: `'horizontal'`, left to right
:obj: `'vertical'`, bottom to top
Example: ::
{
"constraint":"Align",
"instances": ["MN0", "MN1", "MN3"],
"line": "center",
"direction": "horizontal"
}
Note: This is a user-convenience constraint. Same
effect can be realized using `Order` & `Align`
'''
instances: List[str]
line: Literal[
'top', 'bottom',
'left', 'right',
'center'
] = 'bottom'
direction: Optional[Literal['horizontal', 'vertical']]
abut: bool = False
@types.validator('direction', allow_reuse=True, always=True)
def _direction_depends_on_line(cls, v, values):
# Process unambiguous line values
if values['line'] in ['bottom', 'top']:
if v is None:
v = 'horizontal'
else:
assert v == 'horizontal', \
'direction is horizontal if line is bottom or top'
elif values['line'] in ['left', 'right']:
if v is None:
v = 'vertical'
else:
assert v == 'vertical', \
'direction is vertical if line is left or right'
# Center needs both line & direction
elif values['line'] == 'center':
assert v, \
'direction must be specified if line == center'
return v
[docs] def yield_constraints(self):
with set_context(self._parent):
yield Align(
instances=self.instances,
line=f'{self.direction[0]}_{self.line}'
)
yield Order(
instances=self.instances,
direction='left_to_right' if self.direction == 'horizontal' else 'top_to_bottom',
abut=self.abut
)
[docs]class Floorplan(UserConstraint):
'''
Row-based layout floorplan from top to bottom
Instances on each row are ordered from left to right.
Example: Define three regions and assign each instance to a region:
{"constraint":"Floorplan", "regions": [["A", "B", "C"], ["D", "E"], ["G"], "order": true}
-----
A B C
-----
D E
-----
G
-----
'''
regions: List[List[str]]
order: bool = False
symmetrize: bool = False
@types.validator('regions', allow_reuse=True, always=True)
def _check_instance(cls, value):
new_rows = list()
for row in value:
new_rows.append(validate_instances(cls, row))
return new_rows
[docs] def yield_constraints(self):
above_below = set()
with set_context(self._parent):
logger.debug("=== Floorplan ========================")
# Regions from top to bottom
logger.debug("===========================")
for i in range(len(self.regions)-1):
for [above, below] in plain_itertools.product(self.regions[i], self.regions[i+1]):
logger.debug(f'Above:{above} Below:{below}')
above_below.add((above, below))
assert (below, above) not in above_below, \
f'Please review floorplan constraint:\n{self.regions}.\n{below} is previously placed above {above}.'
yield Order(instances=[above, below], direction='top_to_bottom', abut=False)
# Order instances in each region from left to right
if self.order:
logger.debug("===========================")
for region in self.regions:
logger.debug(f'Order left to right: {region}')
if len(region) > 1:
yield Order(instances=region, direction='left_to_right', abut=False)
# Symmetrize instances along a single vertical line
if self.symmetrize:
logger.debug("===========================")
pairs = list()
for region in self.regions:
if len(region) <= 2:
pairs.append(region)
else:
for i in range(len(region)//2):
pairs.append([region[i], region[-1-i]])
if len(region) % 2 == 1:
pairs.append([region[i+1]])
logger.debug(f'Symmetric blocks:\n{pairs}')
yield SymmetricBlocks(pairs=pairs, direction='V')
#
# list of 'SoftConstraint'
#
# Below is a list of legacy constraints
# that have not been hardened yet
#
[docs]class PlaceSymmetric(SoftConstraint):
# TODO: Finish implementing this. Not registered to
# ConstraintDB yet
'''
Place instance / pair of `instances` symmetrically
around line of symmetry along `direction`
Note: This is a user-convenience constraint. Same
effect can be realized using `Align` & `Group`
For example:
`instances` = [['1'], ['4', '5'], ['2', '3'], ['6']]
`direction` = 'vertical'
1 | 5 4 | 6 | 4 5 | 1 | 5 4
4 5 | 1 | 5 4 | 6 | 6 | 1
2 3 | 2 3 | 3 2 | 1 | 5 4 | 6
6 | 6 | 1 | 2 3 | 2 3 | 3 2
'''
instances: List[List[str]]
direction: Optional[Literal['horizontal', 'vertical']]
[docs] @types.validator('instances', allow_reuse=True)
def place_symmetric_instances_validator(cls, value):
'''
X = Align(2, 3, 'h_center')
Y = Align(4, 5, 'h_center')
Align(1, X, Y, 6, 'center')
'''
assert len(value) >= 1, 'Must contain at least one instance'
assert all(isinstance(x, List) for x in value), f'All arguments must be of type list in {cls.instances}'
return value
[docs]class CompactPlacement(SoftConstraint):
"""CompactPlacement
Defines snapping position of placement for all blocks in design.
Args:
style (str): Following options are available.
:obj:`'left'`, Moves all instances towards left during post-processing of placement.
:obj:`'right'`, Moves all instances towards right during post-processing of placement.
:obj:`'center'`, Moves all instances towards center during post-processing of placement.
Example: ::
{"constraint": "CompactPlacement", "style": "center"}
"""
style: Literal[
'left', 'right',
'center'
] = 'left'
[docs]class SameTemplate(SoftConstraint):
"""SameTemplate
Makes identical copy of all isntances
Args:
instances (list[str]): List of :obj:`instances`
Example: ::
{"constraint":"SameTemplate", "instances": ["MN0", "MN1", "MN3"]}
"""
instances: List[str]
[docs]class CreateAlias(SoftConstraint):
"""CreateAlias
Creates an alias for list of instances. You can use this
alias later while defining constraints
Args:
instances (list[str]): List of :obj:`instances`
name (str): alias for the list of :obj:`instances`
Example: ::
{
"constraint":"CreateAlias",
"instances": ["MN0", "MN1", "MN3"],
"name": "alias1"
}
"""
instances: List[str]
name: str
[docs]class PlaceCloser(SoftConstraint):
'''
`instances` are preferred to be placed closer.
'''
instances: List[str]
_inst_validator = types.validator('instances', allow_reuse=True)(validate_instances)
[docs]class PowerPorts(SoftConstraint):
'''
Defines power ports for each hieararchy
Args:
ports (list[str]): List of :obj:`ports`.
The first port of top hierarchy will be used for power grid creation.
Power ports are used to identify source and drain of transistors
by identifying the terminal at higher potential.
Example: ::
{
"constraint":"PowerPorts",
"ports": ["VDD", "VDD1"],
}
'''
ports: List[str]
_upper_case = types.validator('ports', allow_reuse=True)(upper_case)
[docs]class GroundPorts(SoftConstraint):
'''
Ground port for each hieararchy
Args:
ports (list[str]): List of :obj:`ports`.
The first port of top hierarchy will be used for ground grid creation.
Power ports are used to identify source and drain of transistors
by identifying the terminal at higher potential.
Example: ::
{
"constraint": "GroundPorts",
"ports": ["GND", "GNVD1"],
}
'''
ports: List[str]
_upper_case = types.validator('ports', allow_reuse=True)(upper_case)
[docs]class ClockPorts(SoftConstraint):
'''
Clock port for each hieararchy. These are used as stop-points
during auto-constraint identification, means no constraint search
will be done beyond the nets connected to these ports.
Args:
ports (list[str]): List of :obj:`ports`.
Example: ::
{
"constraint": "ClockPorts",
"ports": ["CLK1", "CLK2"],
}
'''
ports: List[str]
_upper_case = types.validator('ports', allow_reuse=True)(upper_case)
[docs]class DoNotUseLib(SoftConstraint):
'''
Primitive libraries which should not be used during hierarchy annotation.
Args:
libraries (list[str]): List of :obj:`libraries`.
propagate: Copy this constraint to sub-hierarchies
Example: ::
{
"constraint": "DoNotUseLib",
"libraries": ["DP_NMOS", "INV"],
"propagate": false
}
'''
libraries: List[str]
propagate: bool = False
[docs]class Generator(SoftConstraint):
'''
Used to guide primitive generator.
Args:
name(str): name of genrator e.g., mos/cap/res/ring
parameters(dict): {
pattern (str): common centroid (cc)/ Inter digitated (id)/Non common centroid (ncc)
parallel_wires (dict): {net_name:2}
body (bool): true/ false
}
Example: ::
{
"constraint": "Generator",
"name": "mos",
"parameters : {
"pattern": "cc",
"parallel_wires": {"net1":2, "net2":2},
"body": true
}
}
'''
name: Optional[str]
parameters: Optional[dict]
[docs]class DoNotIdentify(SoftConstraint):
'''
Stop any auto-grouping of provided instances
Automatically adds instances from all constraint
WARNING: user-defined `groupblock`/`groupcap` constraint will ignore this constraint
'''
instances: List[str]
[docs]class SymmetricBlocks(HardConstraint):
"""SymmetricBlocks
Defines a symmetry constraint between single and/or pairs of blocks.
Args:
pairs (list[list[str]]): List of pair of instances.
A pair can have one :obj:`instance` or two instances,
where single instance implies self-symmetry
direction (str) : Direction for axis of symmetry. Literal::
['V', 'H']
.. image:: ../images/SymmetricBlocks.PNG
:align: center
Example: ::
{
"constraint" : "SymmetricBlocks",
"pairs" : [["MN0","MN1"], ["MN2","MN3"], ["MN4"]],
"direction" : "V"
}
"""
pairs: List[List[str]]
direction: Literal['H', 'V']
[docs] @types.validator('pairs', allow_reuse=True)
def pairs_validator(cls, value):
_ = get_instances_from_hacked_dataclasses(cls._validator_ctx())
if len(value) == 1:
assert len(value[0]) == 2, 'Must contain at least a pair of two instances or more than two pairs.'
for pair in value:
assert len(pair) >= 1, 'Must contain at least one instance'
assert len(pair) <= 2, 'Must contain at most two instances'
value = [validate_instances(cls, pair) for pair in value]
if not hasattr(cls._validator_ctx().parent.parent, 'elements'):
# PnR stage VerilogJsonModule
return value
if len(cls._validator_ctx().parent.parent.elements) == 0:
# skips the check while reading user constraints
return value
group_block_instances = [const.name for const in cls._validator_ctx().parent if isinstance(const, GroupBlocks)]
for pair in value:
# logger.debug(f"pairs {self.pairs} {self.parent.parent.get_element(pair[0])}")
if len([ele for ele in pair if ele in group_block_instances]) > 0:
# Skip check for group block elements as they are added later in the flow
continue
elif len(pair) == 2:
assert cls._validator_ctx().parent.parent.get_element(pair[0]), f"element {pair[0]} not found in design"
assert cls._validator_ctx().parent.parent.get_element(pair[1]), f"element {pair[1]} not found in design"
assert cls._validator_ctx().parent.parent.get_element(pair[0]).parameters == \
cls._validator_ctx().parent.parent.get_element(pair[1]).parameters, \
f"Parameters of the symmetry pair {pair} do not match in subckt {cls._validator_ctx().parent.parent.name}"
return value
[docs] def translate(cls, solver):
def construct_expression(b1, b2=None):
c = 'x' if cls.direction == 'V' else 'y'
expression = getattr(b1, f'll{c}') + getattr(b1, f'ur{c}')
if b2:
expression += getattr(b2, f'll{c}') + getattr(b2, f'ur{c}')
else:
expression += getattr(b1, f'll{c}') + getattr(b1, f'ur{c}')
return expression
for i, instances in enumerate(cls.pairs):
if len(instances) == 2:
b0 = solver.bbox_vars(instances[0])
b1 = solver.bbox_vars(instances[1])
# the difference between the center lines should be <= 1/4th of the block heights
# abs(cl_1 - cl_2) <= height_1/4 && abs(cl_1 - cl_2) <= height_2/4
# abs(4.cl_1 - 4.cl_2) <= height_1, height_2
c = 'y' if cls.direction == 'V' else 'x'
b0_quad_cl = 2*(getattr(b0, f'll{c}') + getattr(b0, f'ur{c}'))
b1_quad_cl = 2*(getattr(b1, f'll{c}') + getattr(b1, f'ur{c}'))
expression = solver.And(
solver.Abs(b0_quad_cl - b1_quad_cl) <= getattr(b0, f'ur{c}') - getattr(b0, f'll{c}'),
solver.Abs(b0_quad_cl - b1_quad_cl) <= getattr(b1, f'ur{c}') - getattr(b1, f'll{c}'),
)
yield expression
# center lines of pairs should match along the direction
if i == 0:
reference = construct_expression(*solver.iter_bbox_vars(instances))
else:
centerline = construct_expression(*solver.iter_bbox_vars(instances))
expression = (reference == centerline)
yield expression
[docs]class OffsetsScalings(BaseModel):
offsets: List[int] = Field(default_factory=lambda: [0])
scalings: List[Literal[-1, 1]] = Field(default_factory=lambda: [1])
[docs]class PlaceOnGrid(SoftConstraint):
direction: Literal['H', 'V']
pitch: int
ored_terms: List[OffsetsScalings] = Field(default_factory=lambda: [OffsetsScalings()])
[docs] @types.validator('ored_terms', allow_reuse=False)
def ored_terms_validator(cls, value, values):
pitch = values['pitch']
for term in value:
for offset in getattr(term, 'offsets'):
assert 0 <= offset < pitch, f'offset {offset} should be less than pitch {pitch}'
return value
[docs]class BlockDistance(SoftConstraint):
'''
TODO: Replace with Spread
Places the instances with a fixed gap.
Also used in situations when routing is congested.
Args:
abs_distance (int) : Distance between two blocks.
The number should be multiple of pitch of
lowest horizontal and vertical routing layer i.e., M2 and M1
.. image:: ../images/HorizontalDistance.PNG
:align: center
Example: ::
{
"constraint" : "BlockDistance",
"abs_distance" : 420
}
'''
abs_distance: int
[docs]class VerticalDistance(SoftConstraint):
'''
TODO: Replace with Spread
Places the instances with a fixed vertical gap.
Also used in situations when routing is congested.
Args:
abs_distance (int) : Distance between two blocks.
The number should be multiple of pitch of
lowest horizontal routing layer i.e., M2
.. image:: ../images/VerticalDistance.PNG
:align: center
Example: ::
{
"constraint" : "VerticalDistance",
"abs_distance" : 84
}
'''
abs_distance: int
[docs]class HorizontalDistance(SoftConstraint):
'''
TODO: Replace with Spread
Places the instances with a fixed horizontal gap.
Also used in situations when routing is congested.
Args:
abs_distance (int) : Distance between two blocks.
The number should be multiple of pitch of
lowest vertical routing layer i.e., M1
.. image:: ../images/HorizontalDistance.PNG
:align: center
Example: ::
{
"constraint" : "HorizontalDistance",
"abs_distance" : 80
}
'''
abs_distance: int
[docs]class GuardRing(SoftConstraint):
'''
Adds guard ring for particular hierarchy.
Args:
guard_ring_primitives (str) : Places this instance across boundary of a hierarchy
global_pin (str): connect the pin of guard ring to this pin, mostly ground pin
block_name: Name of the hierarchy
Example: ::
{
"constraint" : "GuardRing",
"guard_ring_primitives" : "guard_ring",
"global_pin
}
'''
guard_ring_primitives: str
global_pin: str
block_name: str
[docs]class GroupCaps(SoftConstraint):
'''GroupCaps
Creates a common centroid cap using a combination
of unit sized caps. It can be of multiple caps.
Args:
name (str): name for grouped caps
instances (List[str]): list of cap :obj:`instances`
unit_cap (str): Capacitance value in fF
num_units (List[int]): Number of units for each capacitance instance
dummy (bool): Whether to fill in dummies or not
Example: ::
{
"constraint" : "GroupCaps",
"name" : "cap_group1",
"instances" : ["C0", "C1", "C2"],
"num_units" : [2, 4, 8],
"dummy" : true
}
'''
name: str # subcircuit name
instances: List[str]
unit_cap: str # cap value in fF
num_units: List
dummy: bool # whether to fill in dummies
[docs]class NetPriority(SoftConstraint):
"""
Specify a non-negative priority for a list of nets for placement (default = 1).
Example: {"constraint": "NetPriority", "nets": ["en", "enb"], "priority": 0}
"""
nets: List[str]
weight: int
_weight = types.validator('weight', allow_reuse=True)(assert_non_negative)
_upper_case = types.validator('nets', allow_reuse=True)(upper_case)
[docs]class NetConst(SoftConstraint):
"""NetConst
Net based constraint. Shielding and critically can be defined.
Args:
nets (List[str]) : List of net names.
shield (str, optional) : Name of net for shielding.
criticality (int, optional) : Criticality of net.
Higher criticality means the net would be routed first.
Example: ::
{
"constraint" : "NetConst",
"nets" : ["net1", "net2", "net3"],
"shield" : "VSS",
"criticality" : 10
}
"""
nets: List[str]
shield: Optional[str]
criticality: Optional[int]
[docs]class PortLocation(SoftConstraint):
'''PortLocation
Defines approximate location of the port.
T (top), L (left), C (center), R (right), B (bottom)
Args:
ports (List[str]) : List of ports
location (str): Literal::
['TL', 'TC', 'TR',
'RT', 'RC', 'RB',
'BL', 'BC', 'BR',
'LB', 'LC', 'LT']
Example ::
{
"constraint" : "PortLocation",
"ports" : ["P0", "P1", "P2"],
"location" : "TL"
}
'''
ports: List
location: Literal['TL', 'TC', 'TR',
'RT', 'RC', 'RB',
'BL', 'BC', 'BR',
'LB', 'LC', 'LT']
[docs]class SymmetricNets(SoftConstraint):
'''SymmetricNets
Defines two nets as symmetric.
A symmetric net will also enforce a SymmetricBlock between blocks
connected to the nets.
Args:
net1 (str) : Name on net1
net2 (str) : Name of net2
pins1 (List, Optional) : oredered list of connected pins to be matched
pins2 (List, Optional) : oredered list of connected pins to be matched
direction (str) : Literal ['H', 'V'], Horizontal or vertical line of symmetry
Example ::
{
"constraint" : "SymmetricNets",
"net1" : "net1"
"net2" : "net2"
"pins1" : ["block1/A", "block2/A", "port1"]
"pins2" : ["block1/B", "block2/B", "port2"]
"direction" : 'V'
}
'''
net1: str
net2: str
pins1: Optional[List]
pins2: Optional[List]
direction: Literal['H', 'V']
[docs]class MultiConnection(SoftConstraint):
'''MultiConnection
Defines multiple parallel wires for a net.
This constraint is used to reduce parasitics and
Electro-migration (EM) violations
Args:
nets (List[str]) : List of nets
multiplier (int): Number of parallel wires
Example ::
{
"constraint" : "MultiConnection",
"nets" : ["N1", "N2", "N3"],
"multiplier" : 4
}
'''
nets: List[str]
multiplier: int
[docs]class DoNotRoute(SoftConstraint):
nets: List[str]
_upper_case = types.validator('nets', allow_reuse=True)(upper_case)
ConstraintType = Union[
# ALIGN Internal DSL
Order, Align, Floorplan,
Enclose, Spread,
AssignBboxVariables,
AspectRatio,
Boundary,
# Additional User constraints
AlignInOrder,
# Legacy Align constraints
# (SoftConstraints)
CompactPlacement,
Generator,
SameTemplate,
CreateAlias,
GroupBlocks,
PlaceCloser,
DoNotIdentify,
PlaceOnGrid,
BlockDistance,
HorizontalDistance,
VerticalDistance,
GuardRing,
SymmetricBlocks,
GroupCaps,
NetConst,
PortLocation,
SymmetricNets,
MultiConnection,
DoNotRoute,
# Setup constraints
PowerPorts,
GroundPorts,
ClockPorts,
DoNotUseLib,
ConfigureCompiler,
NetPriority
]
[docs]class ConstraintDB(types.List[ConstraintType]):
[docs] @types.validate_arguments
def append(self, constraint: ConstraintType):
if (constraint_str := repr(constraint)) not in self._cache:
if hasattr(constraint, 'translate'):
if self.parent._checker is None:
self.parent.verify()
self.parent.verify(constraint=constraint)
super().append(constraint)
self._cache.add(constraint_str)
else:
logger.debug(f"Constraint is duplicated: {constraint_str}")
[docs] @types.validate_arguments
def remove(self, constraint: ConstraintType):
super().remove(constraint)
def __init__(self, *args, **kwargs):
super().__init__()
# Constraints may need to access parent scope for subcircuit information
# To ensure parent is set appropriately, force users to use append
if '__root__' in kwargs:
data = kwargs['__root__']
del kwargs['__root__']
elif len(args) == 1:
data = args[0]
args = tuple()
else:
assert len(args) == 0 and len(kwargs) == 0
data = []
# TODO: Shouldn't need to invalidate this
# Lots of thrash happening here
self.parent._checker = None
with set_context(self):
for x in data:
super().append(x)
[docs] def checkpoint(self):
if self.parent._checker is None:
self.parent.verify()
self.parent._checker.checkpoint()
return super().checkpoint()
def _revert(self):
self.parent._checker.revert()
super()._revert()
[docs]def expand_user_constraints(const_list):
for const in const_list:
if hasattr(const, 'yield_constraints'):
with types.set_context(const.parent):
yield from const.yield_constraints()
else:
yield const