Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate Python Placer to tool flow #1257

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 20 additions & 10 deletions align/pnr/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from .placer import placer_driver, startup_gui
from .router import router_driver
from .cap_placer import cap_placer_driver
from .placer_pythonic import pythonic_placer_driver

import shutil

Expand Down Expand Up @@ -154,6 +155,7 @@ def gen_leaf_cell_info( verilog_d, pnr_const_ds):
logger.debug( f'leaves: {leaves}')
return leaves, capacitors


def gen_leaf_collateral( leaves, primitives, primitive_dir):

# Check if collateral files exist
Expand All @@ -175,6 +177,7 @@ def gen_leaf_collateral( leaves, primitives, primitive_dir):

return leaf_collateral


def write_verilog_d(verilog_d):
return {"modules":[{"name":m.name,
"parameters": list(m.parameters),
Expand All @@ -183,6 +186,7 @@ def write_verilog_d(verilog_d):
} for m in verilog_d.modules],
"global_signals":verilog_d.global_signals}


def generate_pnr(topology_dir, primitive_dir, pdk_dir, output_dir, subckt, *, primitives, nvariants=1, effort=0, extract=False,
gds_json=False, PDN_mode=False, router_mode='top_down', gui=False, skipGDS=False, steps_to_run,lambda_coeff,
nroutings=1, select_in_ILP=False, place_using_ILP=False, seed=0, use_analytical_placer=False, ilp_solver='symphony',
Expand Down Expand Up @@ -211,7 +215,7 @@ def generate_pnr(topology_dir, primitive_dir, pdk_dir, output_dir, subckt, *, pr
input_dir.mkdir(exist_ok=True)

verilog_d = VerilogJsonTop.parse_file(topology_dir / verilog_file)

manipulate_hierarchy(verilog_d, subckt)

logger.debug(f"updated verilog: {verilog_d}")
Expand Down Expand Up @@ -341,16 +345,22 @@ def generate_pnr(topology_dir, primitive_dir, pdk_dir, output_dir, subckt, *, pr
'nvariants': nvariants,
'effort': effort}

top_level, leaf_map, placement_verilog_alternatives, metrics = \
placer_driver(cap_map=cap_map, cap_lef_s=cap_lef_s,
lambda_coeff=lambda_coeff, scale_factor=scale_factor,
select_in_ILP=select_in_ILP, place_using_ILP=place_using_ILP, seed=seed,
use_analytical_placer=use_analytical_placer, ilp_solver=ilp_solver, primitives=primitives,
toplevel_args_d=toplevel_args_d, results_dir=None,
placer_sa_iterations=placer_sa_iterations, placer_ilp_runtime=placer_ilp_runtime)
if placer == 'python':
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stevenmburns , this is where the Python placer is called. I am dumping pva just for observability. Its structure is as follows (truncated for readability):

{
  "leaves": [
    {
      "abstract_name": "NMOS_3T_85679509",
      "concrete_name": "NMOS_3T_85679509_X2_Y2",
      "constraints": [],
      "bbox": [],
      "terminals": []
    },
    {
      "abstract_name": "DCL_PMOS_2T_69165982",
      "concrete_name": "DCL_PMOS_2T_69165982_X2_Y2",
      "constraints": [],
      "bbox": [],
      "terminals": []
    }
  ],
  "modules": [
    {
      "parameters": [
        "VIN",
        "VOP"
      ],
      "constraints": [],
      "instances": [
        {
          "instance_name": "X_MP0",
          "fa_map": [],
          "abstract_template_name": "DCL_PMOS_2T_69165982",
          "transformation": {
            "oX": 15120,
            "oY": 37800,
            "sX": -1,
            "sY": -1
          },
          "concrete_template_name": "DCL_PMOS_2T_69165982_X2_Y2"
        },
        {
          "instance_name": "X_MN0",
          "fa_map": [],
          "abstract_template_name": "NMOS_3T_85679509",
          "transformation": {
            "oX": 15120,
            "oY": 0,
            "sX": -1,
            "sY": 1
          },
          "concrete_template_name": "NMOS_3T_85679509_X2_Y2"
        }
      ],
      "global_signals": [
        "VCCX",
        "VSSX"
      ],
      "bbox": [],
      "abstract_name": "CKT_COMMON_SOURCE",
      "concrete_name": "CKT_COMMON_SOURCE_0",
      "metrics": {
        "cost": 73270.0,
        "width": 15120,
        "height": 37800,
        "area": 571536000,
        "hpwl": 40700.0
      }
    }
  ]
}

pva = pythonic_placer_driver(subckt, input_dir, scale_factor=scale_factor)
with open("__placement_verilog_alternatives__.json", "wt") as fp2:
json.dump(pva, fp=fp2, indent=2)

with open("__placer_dump__.json", "wt") as fp:
json.dump((top_level, leaf_map, [(nm, verilog_d.dict()) for nm, verilog_d in placement_verilog_alternatives.items()],metrics), fp=fp, indent=2)
else:
top_level, leaf_map, placement_verilog_alternatives, metrics = \
placer_driver(cap_map=cap_map, cap_lef_s=cap_lef_s,
lambda_coeff=lambda_coeff, scale_factor=scale_factor,
select_in_ILP=select_in_ILP, place_using_ILP=place_using_ILP, seed=seed,
use_analytical_placer=use_analytical_placer, ilp_solver=ilp_solver, primitives=primitives,
toplevel_args_d=toplevel_args_d, results_dir=None,
placer_sa_iterations=placer_sa_iterations, placer_ilp_runtime=placer_ilp_runtime)

with open("__placer_dump__.json", "wt") as fp:
json.dump((top_level, leaf_map, [(nm, verilog_d.dict()) for nm, verilog_d in placement_verilog_alternatives.items()],metrics), fp=fp, indent=2)

os.chdir(current_working_dir)

Expand Down
33 changes: 32 additions & 1 deletion align/pnr/placer_pythonic.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import json
import pathlib

from align.schema.hacks import VerilogJsonTop
from align.pnr.checker import check_placement
Expand Down Expand Up @@ -115,3 +116,33 @@ def pythonic_placer(top_level, input_data, scale_factor=1):
placement_data = trim_placement_data(placement_data, top_level)

return placement_data


def pythonic_placer_driver(top_level, input_dir: pathlib.Path, scale_factor=1):

with (input_dir / f'{top_level}.verilog.json').open('rt') as fp:
input_data = json.load(fp)

input_data['leaves'] = list()

modules = {module['name']: module for module in input_data['modules']}
_, _, found_leaves = compute_topoorder(modules, top_level)

for abstract_template_name in found_leaves:
json_files = input_dir.glob(f'{abstract_template_name}*.json')
for filename in json_files:
if not filename.name.endswith('gds.json'):
with (input_dir / filename).open('rt') as fp:
leaf_data = json.load(fp)
del leaf_data['globalRoutes']
del leaf_data['globalRouteGrid']
assert 'bbox' in leaf_data
assert 'terminals' in leaf_data
leaf_data['abstract_template_name'] = abstract_template_name
leaf_data['concrete_template_name'] = filename.stem
leaf_data['constraints'] = list()
# TODO: Append PlaceOnGrid if exists
input_data['leaves'].append(leaf_data)

placement_data = pythonic_placer(top_level, input_data, scale_factor=scale_factor)
return placement_data
56 changes: 46 additions & 10 deletions align/pnr/placer_pythonic_sp.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import mip
import time
import copy
import logging
import itertools
import more_itertools
import networkx as nx
Expand All @@ -11,6 +12,8 @@
from align.pnr.grid_constraints import gen_constraints_for_module
from align.cell_fabric.transformation import Transformation, Rect

logger = logging.getLogger(__name__)


class HyperParameters:
max_sequence_pairs = 100
Expand Down Expand Up @@ -44,7 +47,7 @@ def wrapper(*args, **kwargs):
s = time.time()
returned_value = func(*args, **kwargs)
e = time.time() - s
print(f'Elapsed time: {e:.3f} secs')
logger.debug(f'Elapsed time: {e:.3f} secs')
return returned_value
return wrapper

Expand Down Expand Up @@ -218,7 +221,13 @@ def place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair
net_priority = dict()
for constraint in constraint_schema.expand_user_constraints(constraints):

if isinstance(constraint, constraint_schema.Boundary):
if isinstance(constraint, constraint_schema.AspectRatio):
if ratio_low := getattr(constraint, 'ratio_low', False):
model += model.var_by_name('W') >= ratio_low * model.var_by_name('H')
if ratio_high := getattr(constraint, 'ratio_high', False):
model += model.var_by_name('W') <= ratio_high * model.var_by_name('H')

elif isinstance(constraint, constraint_schema.Boundary):
if max_width := getattr(constraint, 'max_width', False):
model += model.var_by_name('W') <= max_width
if max_height := getattr(constraint, 'max_height', False):
Expand Down Expand Up @@ -250,6 +259,29 @@ def place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair
for i1 in instances[1:]:
model += model.var_by_name(f'{i0}_{axis}') == model.var_by_name(f'{i1}_{axis}')

elif isinstance(constraint, constraint_schema.Order):
instances = getattr(constraint, 'instances')
direction = getattr(constraint, 'direction')
abut = getattr(constraint, 'abut')

def cc(i0, i1, axis, abut):
if abut:
return model.var_by_name(f'{i0}_ur{axis}') == model.var_by_name(f'{i1}_ll{axis}')
else:
return model.var_by_name(f'{i0}_ur{axis}') <= model.var_by_name(f'{i1}_ll{axis}')

for i0, i1 in more_itertools.pairwise(instances):
if direction == 'top_to_bottom':
model += cc(i1, i0, 'y', abut)
elif direction == 'bottom_to_top':
model += cc(i0, i1, 'y', abut)
elif direction == 'left_to_right':
model += cc(i0, i1, 'x', abut)
elif direction == 'right_to_left':
model += cc(i1, i0, 'x', abut)
else:
assert False, f'{direction} not supported'

elif isinstance(constraint, constraint_schema.SymmetricBlocks):
pairs = getattr(constraint, 'pairs')
axis = 'x' if getattr(constraint, 'direction') == 'V' else 'y'
Expand Down Expand Up @@ -304,21 +336,21 @@ def place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair
model.write('model.lp')

# Solve
status = model.optimize(max_seconds_same_incumbent=60.0, max_seconds=300)
status = model.optimize(max_seconds_same_incumbent=60.0, max_seconds=min(10*len(instance_map), 300))
if status == mip.OptimizationStatus.OPTIMAL:
print(f'optimal solution found: cost={model.objective_value}')
logger.debug(f'optimal solution found: cost={model.objective_value}')
elif status == mip.OptimizationStatus.FEASIBLE:
print(f'solution with cost {model.objective_value} current lower bound: {model.objective_bound}')
logger.debug(f'solution with cost {model.objective_value} current lower bound: {model.objective_bound}')
else:
print('No solution to ILP')
logger.debug('No solution to ILP')
return False

# if status == mip.OptimizationStatus.OPTIMAL or status == mip.OptimizationStatus.FEASIBLE:
# if model.verbose:
# print('Solution:')
# logger.debug('Solution:')
# for v in model.vars:
# print('\t', v.name, v.x)
# print(f'Number of solutions: {model.num_solutions}')
# logger.debug(f'Number of solutions: {model.num_solutions}')

# Extract solution
transformations = dict()
Expand All @@ -337,7 +369,7 @@ def place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair
'width': w,
'height': h,
'area': w*h,
'hpwl': model.var_by_name('HPWL').x / scale_hpwl,
'hpwl': model.var_by_name('HPWL').x,
'transformations': transformations,
'model': model
}
Expand All @@ -351,6 +383,9 @@ def place_using_sequence_pairs(placement_data, module, top_level):

hyper_params = HyperParameters()

if 'global_signals' not in module:
module['global_signals'] = set()

instances = {i['instance_name']: i for i in module['instances']}

instance_map = dict()
Expand Down Expand Up @@ -393,7 +428,7 @@ def place_using_sequence_pairs(placement_data, module, top_level):

for formal_actual in instances[instance_name]['fa_map']:
formal, actual = formal_actual['formal'], formal_actual['actual']
if actual not in module['global_signals']:
if 'global_signals' in module and actual not in module['global_signals']:
wires[actual].append((instance_name, tuple(x for x in concrete_template['pin_bbox'][formal])))

solution = place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair, wires=wires, place_on_grid=place_on_grid,
Expand Down Expand Up @@ -449,6 +484,7 @@ def place_using_sequence_pairs(placement_data, module, top_level):
new_module['abstract_name'] = new_module['name']
new_module['concrete_name'] = new_module['name'] + f'_{i}'
new_module['pin_bbox'] = pin_bbox
new_module['metrics'] = {k: solution[k] for k in ['cost', 'width', 'height', 'area', 'hpwl']}
del new_module['name']

placement_data['modules'].append(new_module)
Expand Down
Loading