From d1961468e4dbcaba1fd2c185f9886e3f62f9921a Mon Sep 17 00:00:00 2001 From: soneryaldiz Date: Mon, 7 Nov 2022 17:26:34 -0800 Subject: [PATCH 01/19] placer_pythonic --- align/cmdline.py | 7 +- align/main.py | 4 +- align/pnr/main.py | 2 +- align/pnr/placer_pythonic.py | 74 ++++ align/pnr/placer_pythonic_sp.py | 387 ++++++++++++++++++ tests/placer_pythonic/test_placer_pythonic.py | 109 +++++ .../test_placer_pythonic_sp.py | 196 +++++++++ 7 files changed, 775 insertions(+), 4 deletions(-) create mode 100644 align/pnr/placer_pythonic.py create mode 100644 align/pnr/placer_pythonic_sp.py create mode 100644 tests/placer_pythonic/test_placer_pythonic.py create mode 100644 tests/placer_pythonic/test_placer_pythonic_sp.py diff --git a/align/cmdline.py b/align/cmdline.py index 6a4281764f..4c14ed8326 100644 --- a/align/cmdline.py +++ b/align/cmdline.py @@ -156,11 +156,16 @@ def __init__(self, *args, **kwargs): choices=['symphony', 'lpsolve'], help='ILP Solver used by placer ') - parser.add_argument('--placer_ilp_runtime', type=int, default=1, help="Runtime limit in seconds for ILP in each iteration of placement") + + parser.add_argument('--placer', + choices=['pythonic', 'cpp'], + default='cpp', + help='Select the placer engine to use. Default: %(default)s') + self.parser = parser def parse_args(self, *args, **kwargs): diff --git a/align/main.py b/align/main.py index 2c94b7d421..66d8b30e06 100644 --- a/align/main.py +++ b/align/main.py @@ -123,7 +123,7 @@ def schematic2layout(netlist_dir, pdk_dir, netlist_file=None, subckt=None, worki log_level=None, verbosity=None, generate=False, regression=False, uniform_height=False, PDN_mode=False, flow_start=None, flow_stop=None, router_mode='top_down', gui=False, skipGDS=False, lambda_coeff=1.0, nroutings=1, viewer=False, select_in_ILP=False, place_using_ILP=False, seed=0, use_analytical_placer=False, ilp_solver='symphony', - placer_sa_iterations=10000, placer_ilp_runtime=1): + placer_sa_iterations=10000, placer_ilp_runtime=1, placer=None): steps_to_run = build_steps(flow_start, flow_stop) @@ -191,7 +191,7 @@ def schematic2layout(netlist_dir, pdk_dir, netlist_file=None, subckt=None, worki place_using_ILP=place_using_ILP, seed=seed, use_analytical_placer=use_analytical_placer, ilp_solver=ilp_solver, - placer_sa_iterations=placer_sa_iterations, placer_ilp_runtime=placer_ilp_runtime) + placer_sa_iterations=placer_sa_iterations, placer_ilp_runtime=placer_ilp_runtime, placer=placer) results.append((subckt, variants)) diff --git a/align/pnr/main.py b/align/pnr/main.py index eb152071d3..7a30b67e74 100644 --- a/align/pnr/main.py +++ b/align/pnr/main.py @@ -199,7 +199,7 @@ def write_verilog_d(verilog_d): 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', - placer_sa_iterations=10000, placer_ilp_runtime=1): + placer_sa_iterations=10000, placer_ilp_runtime=1, placer=None): subckt = subckt.upper() diff --git a/align/pnr/placer_pythonic.py b/align/pnr/placer_pythonic.py new file mode 100644 index 0000000000..c4ba719b3d --- /dev/null +++ b/align/pnr/placer_pythonic.py @@ -0,0 +1,74 @@ +from align.schema.hacks import VerilogJsonTop +from align.pnr.checker import check_placement +from align.pnr.placer_pythonic_sp import place_using_sequence_pairs + + +def compute_topoorder(modules, concrete_name, key='abstract_template_name'): + found_modules, found_leaves = set(), set() + order = list() + + def aux(cn): + if cn in modules: + found_modules.add(cn) + for instance in modules[cn]['instances']: + ctn = instance[key] + if ctn not in found_modules and ctn not in found_leaves: + aux(ctn) + order.append(cn) + else: + found_leaves.add(cn) + order.append(cn) + aux(concrete_name) + + return order, found_modules, found_leaves + + +def trim_placement_data(placement_data, top_level): + + modules = {module['concrete_name']: module for module in placement_data['modules']} + + top_concrete_names = [module['concrete_name'] for module in placement_data['modules'] if module['abstract_name'] == top_level] + all_modules_leaves = list() + for concrete_name in top_concrete_names: + _, found_modules, found_leaves = compute_topoorder(modules, concrete_name, key='concrete_template_name') + all_modules_leaves.extend(found_modules) + all_modules_leaves.extend(found_leaves) + + all_modules_leaves = set(all_modules_leaves) + + new_placement_data = {'leaves': list(), 'modules': list()} + new_placement_data['leaves'] = [leaf for leaf in placement_data['leaves'] if leaf['concrete_name'] in all_modules_leaves] + new_placement_data['modules'] = [leaf for leaf in placement_data['modules'] if leaf['concrete_name'] in all_modules_leaves] + + return new_placement_data + + +def pythonic_placer(top_level, input_data, scale_factor=1): + + placement_data = dict() + placement_data['modules'] = list() + placement_data['leaves'] = list() + for leaf in input_data['leaves']: + placement_data['leaves'].append({ + 'abstract_name': leaf['abstract_template_name'], + 'concrete_name': leaf['concrete_template_name'], + 'constraints': leaf['constraints'], + 'bbox': leaf['bbox'], + 'terminals': leaf['terminals']}) + + modules = {module['name']: module for module in input_data['modules']} + topological_order, found_modules, _ = compute_topoorder(modules, top_level) + + for name in topological_order: + if name not in found_modules: + continue + + # Select and call placement algorithm here + place_using_sequence_pairs(placement_data, modules[name], top_level) + + check_placement(VerilogJsonTop.parse_obj(placement_data), scale_factor) + + # Trim unused modules and leaves + placement_data = trim_placement_data(placement_data, top_level) + + return placement_data diff --git a/align/pnr/placer_pythonic_sp.py b/align/pnr/placer_pythonic_sp.py new file mode 100644 index 0000000000..48c2a8f3db --- /dev/null +++ b/align/pnr/placer_pythonic_sp.py @@ -0,0 +1,387 @@ +import mip +import time +import copy +import itertools +import more_itertools +import networkx as nx +from collections import defaultdict + +import align.schema.constraint as constraint_schema +from align.schema.hacks import VerilogJsonTop +from align.pnr.grid_constraints import gen_constraints_for_module + + +class HyperParameters(): + max_sequence_pairs = 100 + max_block_variants = 100 + max_candidates = 10000 + max_solutions = 1 + + +def check_place_on_grid(instance, constraints): + for const in constraints: + + if const['constraint'] != 'PlaceOnGrid': + continue + + axis = 'Y' if const['direction'] == 'H' else 'X' + o, s = instance['transformation'][f'o{axis}'], instance['transformation'][f's{axis}'] + + is_satisfied = False + for term in const['ored_terms']: + for offset in term['offsets']: + if (o - offset) % const['pitch'] == 0 and s in term['scalings']: + is_satisfied = True + break + if is_satisfied: + break + assert is_satisfied, f'{instance} does not satisfy {const}' + + +def measure_time(func): + def wrapper(*args, **kwargs): + s = time.time() + returned_value = func(*args, **kwargs) + e = time.time() - s + print(f'Elapsed time: {e:.3f} secs') + return returned_value + return wrapper + + +def enumerate_sequence_pairs(constraints, instance_map: dict, max_sequences: int): + + # Initialize constraint graphs + pos_graph = nx.DiGraph() + neg_graph = nx.DiGraph() + pos_graph.add_nodes_from(range(len(instance_map))) + neg_graph.add_nodes_from(range(len(instance_map))) + + # Add edges to constraint graphs + for constraint in constraint_schema.expand_user_constraints(constraints): + if isinstance(constraint, constraint_schema.Order): + for i0, i1 in more_itertools.pairwise(constraint.instances): + if constraint.direction == 'left_to_right': # (before,before) + pos_graph.add_edge(instance_map[i0], instance_map[i1]) + neg_graph.add_edge(instance_map[i0], instance_map[i1]) + + elif constraint.direction == 'right_to_left': # (after, after) + pos_graph.add_edge(instance_map[i1], instance_map[i0]) + neg_graph.add_edge(instance_map[i1], instance_map[i0]) + + elif constraint.direction == 'bottom_to_top': # (after, before) + pos_graph.add_edge(instance_map[i1], instance_map[i0]) + neg_graph.add_edge(instance_map[i0], instance_map[i1]) + + elif constraint.direction == 'top_to_bottom': # (before, after) + pos_graph.add_edge(instance_map[i0], instance_map[i1]) + neg_graph.add_edge(instance_map[i1], instance_map[i0]) + else: + pass + + # Generate sequences using topological sort + count = 1 + sequence_pairs = list() + for pos in nx.all_topological_sorts(pos_graph): + for neg in nx.all_topological_sorts(neg_graph): + sequence_pairs.append((tuple(pos), tuple(neg))) + count += 1 + if count > max_sequences: + break + if count > max_sequences: + break + + return sequence_pairs + + +def enumerate_variants(constraints, instance_map: dict, variant_counts: dict, max_variants: int): + + # Group instances that should use the same concrete template + groups = list() + grouped_instances = set() + for constraint in constraint_schema.expand_user_constraints(constraints): + if isinstance(constraint, constraint_schema.SameTemplate): + set_of_instances = set(constraint.instances) + grouped_instances = set.union(grouped_instances, set_of_instances) + group_exists = False + for i, group in enumerate(groups): + if set.intersection(set_of_instances, group): + groups[i] = set.union(set_of_instances, group) + group_exists = True + break + if not group_exists: + groups.append(set_of_instances) + + # Create groups for isolated instances + for instance in instance_map.keys(): + if instance not in grouped_instances: + groups.append({instance}) + + # Enumerate + group_variants = list() + for i, group in enumerate(groups): + for instance in group: # get an instance from the set + break + group_variants.append([k for k in range(variant_counts[instance])]) + + count = 1 + variants = list() + for variant in itertools.product(*group_variants): + selection = [0]*len(instance_map) + for i, v in enumerate(variant): + # Assign variant v to all instances of i^th group + for instance in groups[i]: + selection[instance_map[instance]] = v + variants.append(tuple(selection)) + if count > max_variants: + break + return variants + + +def place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair, wires=None, place_on_grid=None): + + model = mip.Model(sense=mip.MINIMIZE, solver_name=mip.CBC) + model.verbose = 0 # set to one to see more progress output with the solver + + upper_bound = 1e9 # 100mm=100e6nm=1e9 angstrom + model.add_var(name='W', lb=0, ub=upper_bound) + model.add_var(name='H', lb=0, ub=upper_bound) + + for name, _ in instance_map.items(): + + size = dict(zip('xy', instance_sizes[name])) + + # Define instance variables + for tag in ['llx', 'lly', 'urx', 'ury']: + model.add_var(name=f'{name}_{tag}', lb=0, ub=upper_bound) + for tag in ['fx', 'fy']: + model.add_var(name=f'{name}_{tag}', var_type=mip.BINARY) + + # Instance width and height + model += model.var_by_name(f'{name}_urx') - model.var_by_name(f'{name}_llx') == size['x'] + model += model.var_by_name(f'{name}_ury') - model.var_by_name(f'{name}_lly') == size['y'] + + # All instances within the bounding box + model += model.var_by_name(f'{name}_urx') <= model.var_by_name('W') + model += model.var_by_name(f'{name}_ury') <= model.var_by_name('H') + + # PlaceOnGrid constraints + if place_on_grid and name in place_on_grid: + assert len(place_on_grid[name]) <= 2 + for constraint in place_on_grid[name]: + axis = 'y' if constraint['direction'] == 'H' else 'x' + pitch = constraint['pitch'] + flip = model.var_by_name(f'{name}_f{axis}') + + offset_scalings = defaultdict(list) + offset_variables = list() + for term in constraint['ored_terms']: + offsets = term['offsets'] + scalings = term['scalings'] + assert set(scalings) in [{1}, {-1}, {-1, 1}] + for offset in offsets: + offset_scalings[offset].extend(scalings) + + for offset, scalings in offset_scalings.items(): + var = model.add_var(var_type=mip.BINARY) + offset_variables.append((offset, var)) + if set(scalings) == {1}: + model += var + flip <= 1 + elif set(scalings) == {-1}: + model += var <= flip + + model += mip.xsum(var[1] for var in offset_variables) == 1 + grid = model.add_var(name=f'{name}_grid_{axis}', var_type=mip.INTEGER, lb=0) + origin = grid*pitch + mip.xsum(v[0]*v[1] for v in offset_variables) + model += origin - size[axis] * flip == model.var_by_name(f'{name}_ll{axis}') + + # Half perimeter wire length + model.add_var(name='HPWL', lb=0, ub=upper_bound) + if wires: + for wire_name, terminals in wires: + for tag, axis in itertools.product(['ll', 'ur'], ['x', 'y']): + model.add_var(name=f'{wire_name}_{tag}{axis}') + + for instance, bbox in terminals: + size = dict(zip("xy", instance_sizes[instance])) + for (tag, axis), offset in zip(itertools.product(['ll', 'ur'], ['x', 'y']), bbox): + eqn = model.var_by_name(f'{instance}_ll{axis}') + offset + (size[axis] - 2*offset) * model.var_by_name(f'{instance}_f{axis}') + model += eqn <= model.var_by_name(f'{wire_name}_ur{axis}') + model += model.var_by_name(f'{wire_name}_ll{axis}') <= eqn + + model += \ + mip.xsum(model.var_by_name(f'{wire_name}_ur{axis}') for wire_name, _ in wires for axis in ['x', 'y']) - \ + mip.xsum(model.var_by_name(f'{wire_name}_ll{axis}') for wire_name, _ in wires for axis in ['x', 'y']) == model.var_by_name('HPWL') + + else: + model += model.var_by_name('HPWL') == 0 + + # Constraints implied by the sequence pairs + reverse_map = {v: k for k, v in instance_map.items()} + instance_pos = {reverse_map[index]: i for i, index in enumerate(sequence_pair[0])} + instance_neg = {reverse_map[index]: i for i, index in enumerate(sequence_pair[1])} + for index0, index1 in itertools.combinations(reverse_map, 2): + name0 = reverse_map[index0] + name1 = reverse_map[index1] + assert name0 != name1 + if instance_pos[name0] < instance_pos[name1] and instance_neg[name0] < instance_neg[name1]: # bb = LEFT + model += model.var_by_name(f'{name0}_urx') <= model.var_by_name(f'{name1}_llx') + elif instance_pos[name0] > instance_pos[name1] and instance_neg[name0] > instance_neg[name1]: # aa = RIGHT + model += model.var_by_name(f'{name1}_urx') <= model.var_by_name(f'{name0}_llx') + elif instance_pos[name0] < instance_pos[name1] and instance_neg[name0] > instance_neg[name1]: # ba = ABOVE + model += model.var_by_name(f'{name1}_ury') <= model.var_by_name(f'{name0}_lly') + elif instance_pos[name0] > instance_pos[name1] and instance_neg[name0] < instance_neg[name1]: # ab = BELOW + model += model.var_by_name(f'{name0}_ury') <= model.var_by_name(f'{name1}_lly') + else: + assert False + + # Placement constraints + for constraint in constraints: + + if 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): + model += model.var_by_name('H') <= max_height + + if isinstance(constraint, constraint_schema.PlaceOnBoundary): + for name in constraint.instances_on(['north', 'northwest', 'northeast']): + model += model.var_by_name(f'{name}_ury') == model.var_by_name('H') + for name in constraint.instances_on(['south', 'southwest', 'southeast']): + model += model.var_by_name(f'{name}_lly') == 0 + for name in constraint.instances_on(['east', 'northeast', 'southeast']): + model += model.var_by_name(f'{name}_urx') == model.var_by_name('W') + for name in constraint.instances_on(['west', 'northwest', 'southwest']): + model += model.var_by_name(f'{name}_llx') == 0 + + # TODO: Placement constraints except Order (sequence pair already satisfies order) + + # Minimize the perimeter of the bounding box + model.objective += model.var_by_name('W') + model.var_by_name('H') + model.var_by_name('HPWL') + + model.write('model.lp') + + # Solve + status = model.optimize(max_seconds_same_incumbent=60.0, max_seconds=300) + if status == mip.OptimizationStatus.OPTIMAL: + print(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}') + else: + print('No solution to ILP') + return False + + # if status == mip.OptimizationStatus.OPTIMAL or status == mip.OptimizationStatus.FEASIBLE: + # if model.verbose: + # print('Solution:') + # for v in model.vars: + # print('\t', v.name, v.x) + # print(f'Number of solutions: {model.num_solutions}') + + # Extract solution + transformations = dict() + for name, _ in instance_map.items(): + fx, fy = model.var_by_name(f'{name}_fx').x, model.var_by_name(f'{name}_fy').x + ox = model.var_by_name(f'{name}_llx').x + fx * instance_sizes[name][0] + oy = model.var_by_name(f'{name}_lly').x + fy * instance_sizes[name][1] + sx = 1 if fx == 0 else -1 + sy = 1 if fy == 0 else -1 + transformations[name] = {'oX': int(ox), 'oY': int(oy), 'sX': sx, 'sY': sy} + + w = int(model.var_by_name('W').x) + h = int(model.var_by_name('H').x) + solution = { + 'cost': model.objective.x, + 'width': w, + 'height': h, + 'area': w*h, + 'hpwl': model.var_by_name('HPWL').x, + 'transformations': transformations, + 'model': model + } + return solution + + +def place_using_sequence_pairs(placement_data, module, top_level): + + ATN = 'abstract_template_name' + CTN = 'concrete_template_name' + + hyper_params = HyperParameters() + + instances = {i['instance_name']: i for i in module['instances']} + + instance_map = dict() + abstract_names = set() + cnt = 0 + for instance in module['instances']: + instance_map[instance['instance_name']] = cnt + abstract_names.add(instance[ATN]) + cnt += 1 + assert cnt > 0, f'Module has no instances: {module}' + + reverse_instance_map = {v: k for k, v in instance_map.items()} + + variant_map = defaultdict(list) + for leaf in placement_data['leaves'] + placement_data['modules']: + if leaf['abstract_name'] in abstract_names: + variant_map[leaf['abstract_name']].append(leaf) + + variant_counts = dict() + for instance in module['instances']: + variant_counts[instance['instance_name']] = len(variant_map[instance[ATN]]) + + verilog_json = VerilogJsonTop.parse_obj({'modules': [module]}) + constraints = verilog_json['modules'][0]['constraints'] + + sequence_pairs = enumerate_sequence_pairs(constraints, instance_map, hyper_params.max_sequence_pairs) + variants = enumerate_variants(constraints, instance_map, variant_counts, hyper_params.max_block_variants) + + solutions = list() + cnt = 0 + for sequence_pair, variant_selection in itertools.product(sequence_pairs, variants): + if cnt > hyper_params.max_candidates: + break + cnt += 1 + print(f'{sequence_pair=}, {variant_selection=}') + + instance_sizes = dict() + place_on_grid = dict() + for idx, selected in enumerate(variant_selection): + instance_name = reverse_instance_map[idx] + concrete_template = variant_map[instances[instance_name][ATN]][selected] + bbox = concrete_template['bbox'] + instance_sizes[instance_name] = [bbox[2] - bbox[0], bbox[3] - bbox[1]] + place_on_grid[instance_name] = [c for c in concrete_template.get('constraints', list()) if c['constraint'] == 'PlaceOnGrid'] + + solution = place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair, place_on_grid=place_on_grid) + if solution: + solution['sequence_pair'] = sequence_pair + solution['variant_selection'] = variant_selection + solutions.append(solution) + + assert solutions, f'No placement solution found for {module}' + + # Annotate best K solutions to placement_data + solutions.sort(key=lambda x: x['cost']) + + max_solutions = hyper_params.max_solutions if module['name'] == top_level else 1 + for i in range(max_solutions): + solution = solutions[i] + new_module = copy.deepcopy(module) + for instance in new_module['instances']: + instance_name = instance['instance_name'] + instance['transformation'] = solution['transformations'][instance_name] + variant_index = solution['variant_selection'][instance_map[instance_name]] + instance[CTN] = variant_map[instance[ATN]][variant_index]['concrete_name'] + check_place_on_grid(instance, variant_map[instance[ATN]][variant_index]['constraints']) + + new_module['bbox'] = [0, 0, solution['width'], solution['height']] + new_module['abstract_name'] = new_module['name'] + new_module['concrete_name'] = new_module['name'] + f'_{i}' + del new_module['name'] + + placement_data['modules'].append(new_module) + + modules = {module['concrete_name']: module for module in placement_data['modules']} + leaves = {leaf['concrete_name']: leaf for leaf in placement_data['leaves']} + gen_constraints_for_module(new_module, modules, leaves) diff --git a/tests/placer_pythonic/test_placer_pythonic.py b/tests/placer_pythonic/test_placer_pythonic.py new file mode 100644 index 0000000000..9b658ea087 --- /dev/null +++ b/tests/placer_pythonic/test_placer_pythonic.py @@ -0,0 +1,109 @@ +from align.pnr.placer_pythonic import pythonic_placer + + +def ring_oscillator(): + place_on_grid = [ + {'constraint': 'PlaceOnGrid', 'direction': 'H', 'pitch': 12600, 'ored_terms': [{'offsets': [0], 'scalings': [1, -1]}]}, + {'constraint': 'PlaceOnGrid', 'direction': 'V', 'pitch': 1080, 'ored_terms': [{'offsets': [0], 'scalings': [1, -1]}]} + ] + data = { + 'modules': [ + { + 'name': 'ring_oscillator_stage', + 'parameters': ['VI', 'VO', 'VCCX', 'VSSX'], + 'constraints': [ + {'constraint': 'Order', 'instances': ['X_MP0', 'X_MN0'], 'direction': 'top_to_bottom'} + ], + 'instances': [ + { + 'instance_name': 'X_MN0', 'abstract_template_name': 'NMOS_3T', + 'fa_map': [{'formal': 'D', 'actual': 'VO'}, {'formal': 'G', 'actual': 'VI'}, {'formal': 'S', 'actual': 'VSSX'}] + }, + { + 'instance_name': 'X_MP0', 'abstract_template_name': 'PMOS_3T', + 'fa_map': [{'formal': 'D', 'actual': 'VO'}, {'formal': 'G', 'actual': 'VI'}, {'formal': 'S', 'actual': 'VCCX'}] + } + ] + }, + { + 'name': 'ring_oscillator', + 'parameters': ['VO', 'VCCX', 'VSSX'], + 'constraints': [ + {'constraint': 'Order', 'instances': ['XI1', 'XI2', 'XI3', 'XI4', 'XI5'], 'direction': 'left_to_right'}, + {'constraint': 'SameTemplate', 'instances': ['XI1', 'XI2', 'XI3', 'XI4', 'XI5']} + ], + 'instances': [ + { + 'instance_name': 'XI1', 'abstract_template_name': 'ring_oscillator_stage', + 'fa_map': [{'formal': 'VCCX', 'actual': 'VCCX'}, {'formal': 'VSSX', 'actual': 'VSSX'}, + {'formal': 'VI', 'actual': 'VO'}, {'formal': 'VO', 'actual': 'V1'}] + }, + { + 'instance_name': 'XI2', 'abstract_template_name': 'ring_oscillator_stage', + 'fa_map': [{'formal': 'VCCX', 'actual': 'VCCX'}, {'formal': 'VSSX', 'actual': 'VSSX'}, + {'formal': 'VI', 'actual': 'V1'}, {'formal': 'VO', 'actual': 'V2'}] + }, + { + 'instance_name': 'XI3', 'abstract_template_name': 'ring_oscillator_stage', + 'fa_map': [{'formal': 'VCCX', 'actual': 'VCCX'}, {'formal': 'VSSX', 'actual': 'VSSX'}, + {'formal': 'VI', 'actual': 'V2'}, {'formal': 'VO', 'actual': 'V3'}] + }, + { + 'instance_name': 'XI4', 'abstract_template_name': 'ring_oscillator_stage', + 'fa_map': [{'formal': 'VCCX', 'actual': 'VCCX'}, {'formal': 'VSSX', 'actual': 'VSSX'}, + {'formal': 'VI', 'actual': 'V3'}, {'formal': 'VO', 'actual': 'V4'}] + }, + { + 'instance_name': 'XI5', 'abstract_template_name': 'ring_oscillator_stage', + 'fa_map': [{'formal': 'VCCX', 'actual': 'VCCX'}, {'formal': 'VSSX', 'actual': 'VSSX'}, + {'formal': 'VI', 'actual': 'V4'}, {'formal': 'VO', 'actual': 'VO'}] + } + ] + } + ], + 'leaves': [ + { + 'abstract_template_name': 'NMOS_3T', + 'concrete_template_name': 'NMOS_3T_X1_Y2', + 'bbox': [0, 0, 6480, 12600], + 'terminals': [], + 'constraints': place_on_grid + }, + { + 'abstract_template_name': 'NMOS_3T', + 'concrete_template_name': 'NMOS_3T_X2_Y1', + 'bbox': [0, 0, 10800, 6300], + 'terminals': [], + 'constraints': place_on_grid + }, + { + 'abstract_template_name': 'PMOS_3T', + 'concrete_template_name': 'PMOS_3T_X1_Y2', + 'bbox': [0, 0, 6480, 12600], + 'terminals': [], + 'constraints': place_on_grid + }, + { + 'abstract_template_name': 'PMOS_3T', + 'concrete_template_name': 'PMOS_3T_X2_Y1', + 'bbox': [0, 0, 10800, 6300], + 'terminals': [], + 'constraints': place_on_grid + } + ] + } + return data + + +def test_place_ring_oscillator(): + input_data = ring_oscillator() + placement_data = pythonic_placer('ring_oscillator', input_data) + assert len(placement_data['leaves']) == 2 + assert len(placement_data['modules']) == 2 + + +def test_place_ring_oscillator_stage(): + input_data = ring_oscillator() + placement_data = pythonic_placer('ring_oscillator_stage', input_data) + assert len(placement_data['leaves']) == 2 + assert len(placement_data['modules']) == 1 diff --git a/tests/placer_pythonic/test_placer_pythonic_sp.py b/tests/placer_pythonic/test_placer_pythonic_sp.py new file mode 100644 index 0000000000..7d43a71cc3 --- /dev/null +++ b/tests/placer_pythonic/test_placer_pythonic_sp.py @@ -0,0 +1,196 @@ +import itertools +import numpy as np +import plotly.graph_objects as go +import plotly.express as px +import pytest + +import align.schema.constraint as constraint_schema +from align.schema.types import set_context +from align.schema import Model, Instance, SubCircuit, Library +from align.pnr.placer_pythonic_sp import enumerate_sequence_pairs, enumerate_variants, place_sequence_pair + +DRAW = False + + +def draw(model, instance_map, instance_sizes, wires): + + colorscale = px.colors.qualitative.Alphabet + + fig = go.Figure() + fig.update_xaxes(range=[0, model.var_by_name('W').x+1]) + fig.update_yaxes(range=[0, model.var_by_name('H').x+1]) + + # Add shapes + x_lst = list() + y_lst = list() + n_lst = list() + for name, i in instance_map.items(): + llx, lly = model.var_by_name(f'{name}_llx').x, model.var_by_name(f'{name}_lly').x + urx, ury = model.var_by_name(f'{name}_urx').x, model.var_by_name(f'{name}_ury').x + assert urx > llx and ury > lly + color = colorscale[i % len(colorscale)] + fig.add_shape(type="rect", x0=llx, y0=lly, x1=urx, y1=ury, line=dict(color="RoyalBlue", width=3), fillcolor=color) + x_lst.append((llx+urx)/2) + y_lst.append((lly+ury)/2) + n_lst.append(f"${name}$") + + i = 0 + for name, terminals in wires: + color = colorscale[i % len(colorscale)] + for instance, bbox in terminals: + size = dict(zip("xy", instance_sizes[instance])) + bb = dict() + for (tag, axis), offset in zip(itertools.product(['ll', 'ur'], ['x', 'y']), bbox): + bb[tag+axis] = model.var_by_name(f'{instance}_ll{axis}').x + offset + \ + (size[axis] - 2*offset) * model.var_by_name(f'{instance}_f{axis}').x + llx, urx = min(bb['llx'], bb['urx']), max(bb['llx'], bb['urx']) + lly, ury = min(bb['lly'], bb['ury']), max(bb['lly'], bb['ury']) + fig.add_shape(type="rect", x0=llx, y0=lly, x1=urx, y1=ury, line=dict(color="Black", width=1), fillcolor=color) + x_lst.append((llx+urx)/2) + y_lst.append((lly+ury)/2) + n_lst.append(f"${name}$") + i += 1 + + fig.update_shapes(opacity=0.5, xref="x", yref="y") + + # Add labels + fig.add_trace(go.Scatter(x=x_lst, y=y_lst, text=n_lst, mode="text", textfont=dict(color="black", size=196))) + + fig.show() + + +def initialize_constraints(n): + library = Library() + with set_context(library): + model = Model(name='TwoTerminalDevice', pins=['A', 'B'], parameters={}) + library.append(model) + subckt = SubCircuit(name='SUBCKT', pins=['PIN1', 'PIN2'], parameters={}) + library.append(subckt) + with set_context(subckt.elements): + for i in range(n): + subckt.elements.append(Instance(name=f'M{i}', model='TwoTerminalDevice', pins={'A': 'PIN1', 'B': 'PIN2'})) + instance_map = {f'M{i}': i for i in range(n)} + return subckt.constraints, instance_map + + +def test_enumerate_sequence_pairs(): + + constraints, instance_map = initialize_constraints(2) + sequence_pairs = enumerate_sequence_pairs(constraints, instance_map, 100) + assert len(sequence_pairs) == 4 + + constraints, instance_map = initialize_constraints(4) + sequence_pairs = enumerate_sequence_pairs(constraints, instance_map, 1000) + assert len(sequence_pairs) == 576 + + constraints, instance_map = initialize_constraints(20) + sequence_pairs = enumerate_sequence_pairs(constraints, instance_map, 1000) + assert len(sequence_pairs) == 1000 + + constraints, instance_map = initialize_constraints(4) + with set_context(constraints): + constraints.append(constraint_schema.Order(direction='left_to_right', instances=[f'M{i}' for i in range(4)])) + sequence_pairs = enumerate_sequence_pairs(constraints, instance_map, 1000) + assert len(sequence_pairs) == 1 + assert sequence_pairs[0] == ((0, 1, 2, 3), (0, 1, 2, 3)) + + constraints, instance_map = initialize_constraints(4) + with set_context(constraints): + constraints.append(constraint_schema.Order(direction='top_to_bottom', instances=[f'M{i}' for i in range(4)])) + sequence_pairs = enumerate_sequence_pairs(constraints, instance_map, 1000) + assert len(sequence_pairs) == 1 + assert sequence_pairs[0] == ((0, 1, 2, 3), (3, 2, 1, 0)) + + +def test_enumerate_variants(): + + constraints, instance_map = initialize_constraints(2) + variant_counts = {"M0": 4, "M1": 5} + variants = enumerate_variants(constraints, instance_map, variant_counts, 100) + assert len(variants) == 20 + + constraints, instance_map = initialize_constraints(4) + variant_counts = {k: 2 for k in instance_map.keys()} + with set_context(constraints): + constraints.append(constraint_schema.SameTemplate(instances=[f'M{i}' for i in range(4)])) + variants = enumerate_variants(constraints, instance_map, variant_counts, 100) + assert len(variants) == 2 + assert variants == [tuple([0]*4), tuple([1]*4)] + + constraints, instance_map = initialize_constraints(4) + variant_counts = {k: 3 for k in instance_map.keys()} + with set_context(constraints): + constraints.append(constraint_schema.SameTemplate(instances=["M1", "M2"])) + constraints.append(constraint_schema.SameTemplate(instances=["M2", "M3"])) + variants = enumerate_variants(constraints, instance_map, variant_counts, 100) + assert len(variants) == 3*3 + + +def test_place_sequence_pair_1(): + n = 2 + constraints, instance_map = initialize_constraints(n) + instance_sizes = {"M0": (9, 5), "M1": (4, 2)} + sequence_pair = ((0, 1), (0, 1)) + c = [ + {"constraint": "place_on_grid", "direction": "H", "pitch": 2, "ored_terms": [{'offsets': [0], 'scalings': [-1, 1]}]}, + {"constraint": "place_on_grid", "direction": "V", "pitch": 2, "ored_terms": [{'offsets': [0], 'scalings': [-1, 1]}]} + ] + wires = [ + ('net1', [('M0', (1, 2, 2, 2.25)), ('M1', (3, 1, 4, 1.25))]) + ] + place_on_grid = {f"M{i}": c for i in range(n)} + solution = place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair, wires=wires, place_on_grid=place_on_grid) + assert solution['transformations']['M0'] == {'oX': 10, 'oY': 0, 'sX': -1, 'sY': 1} + assert solution['transformations']['M1'] == {'oX': 14, 'oY': 4, 'sX': -1, 'sY': -1} + if DRAW: + draw(solution['model'], instance_map, instance_sizes, wires) + + +@pytest.mark.parametrize("iter", [i for i in range(10)]) +def test_place_sequence_pair_2(iter): + n = 10 + constraints, instance_map = initialize_constraints(n) + instance_sizes = {f"M{k}": (1+(1*k)//2, 10) for k in range(n)} + sequence_pair = (list(np.random.permutation(n)), list(np.random.permutation(n))) + c = [ + {"constraint": "place_on_grid", "direction": "H", "pitch": 10, "ored_terms": [{'offsets': [0], 'scalings': [-1, 1]}]}, + {"constraint": "place_on_grid", "direction": "V", "pitch": 2, "ored_terms": [{'offsets': [0], 'scalings': [-1, 1]}]} + ] + place_on_grid = {f"M{i}": c for i in range(n)} + solution = place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair, place_on_grid=place_on_grid) + if DRAW: + draw(solution['model'], instance_map, [], []) + + +def test_place_sequence_pair_boundary(): + n = 5 + constraints, instance_map = initialize_constraints(n) + with set_context(constraints): + constraints.append(constraint_schema.Boundary(subcircuit="subckt", max_height=10, max_width=8)) + instance_sizes = {f"M{k}": (2, 2) for k in range(n)} + sequence_pair = ([k for k in range(n)], [k for k in range(n)]) + assert not place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair) + + with set_context(constraints): + constraints.pop() + constraints.append(constraint_schema.Boundary(subcircuit="subckt", max_height=10, max_width=10)) + solution = place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair) + if DRAW: + draw(solution['model'], instance_map, [], []) + for v in solution['model'].vars: + if v.name == 'H': + break + assert v.x <= 10 + + +def test_place_sequence_pair_place_on_boundary(): + n = 4 + constraints, instance_map = initialize_constraints(n) + with set_context(constraints): + constraints.append(constraint_schema.Boundary(subcircuit="subckt", max_height=10, max_width=10)) + constraints.append(constraint_schema.PlaceOnBoundary(northwest="M0", northeast="M1", southwest="M2", southeast="M3")) + instance_sizes = {f"M{k}": (1+k, 1+k) for k in range(n)} + sequence_pair = ([0, 1, 2, 3], [2, 3, 0, 1]) + solution = place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair) + if DRAW: + draw(solution['model'], instance_map, [], []) From 45ecade982863b2e5828325c47897d534d337bc4 Mon Sep 17 00:00:00 2001 From: soneryaldiz Date: Mon, 7 Nov 2022 17:34:51 -0800 Subject: [PATCH 02/19] dump input and output data --- tests/placer_pythonic/test_placer_pythonic.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/placer_pythonic/test_placer_pythonic.py b/tests/placer_pythonic/test_placer_pythonic.py index 9b658ea087..59aad9f449 100644 --- a/tests/placer_pythonic/test_placer_pythonic.py +++ b/tests/placer_pythonic/test_placer_pythonic.py @@ -1,3 +1,4 @@ +import json from align.pnr.placer_pythonic import pythonic_placer @@ -97,9 +98,13 @@ def ring_oscillator(): def test_place_ring_oscillator(): input_data = ring_oscillator() + with open('placement_input.json', "wt") as fp: + fp.write(json.dumps(input_data, indent=2) + '\n') placement_data = pythonic_placer('ring_oscillator', input_data) assert len(placement_data['leaves']) == 2 assert len(placement_data['modules']) == 2 + with open('placement_output.json', "wt") as fp: + json.dump(placement_data, fp=fp, indent=2) def test_place_ring_oscillator_stage(): From 3365c6fef6655515d6d1985777b2479f14b9b040 Mon Sep 17 00:00:00 2001 From: soneryaldiz Date: Mon, 7 Nov 2022 17:36:57 -0800 Subject: [PATCH 03/19] cosmetic change --- tests/placer_pythonic/test_placer_pythonic.py | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/tests/placer_pythonic/test_placer_pythonic.py b/tests/placer_pythonic/test_placer_pythonic.py index 59aad9f449..3985a45195 100644 --- a/tests/placer_pythonic/test_placer_pythonic.py +++ b/tests/placer_pythonic/test_placer_pythonic.py @@ -8,6 +8,36 @@ def ring_oscillator(): {'constraint': 'PlaceOnGrid', 'direction': 'V', 'pitch': 1080, 'ored_terms': [{'offsets': [0], 'scalings': [1, -1]}]} ] data = { + 'leaves': [ + { + 'abstract_template_name': 'NMOS_3T', + 'concrete_template_name': 'NMOS_3T_X1_Y2', + 'bbox': [0, 0, 6480, 12600], + 'terminals': [], + 'constraints': place_on_grid + }, + { + 'abstract_template_name': 'NMOS_3T', + 'concrete_template_name': 'NMOS_3T_X2_Y1', + 'bbox': [0, 0, 10800, 6300], + 'terminals': [], + 'constraints': place_on_grid + }, + { + 'abstract_template_name': 'PMOS_3T', + 'concrete_template_name': 'PMOS_3T_X1_Y2', + 'bbox': [0, 0, 6480, 12600], + 'terminals': [], + 'constraints': place_on_grid + }, + { + 'abstract_template_name': 'PMOS_3T', + 'concrete_template_name': 'PMOS_3T_X2_Y1', + 'bbox': [0, 0, 10800, 6300], + 'terminals': [], + 'constraints': place_on_grid + } + ], 'modules': [ { 'name': 'ring_oscillator_stage', @@ -61,36 +91,6 @@ def ring_oscillator(): } ] } - ], - 'leaves': [ - { - 'abstract_template_name': 'NMOS_3T', - 'concrete_template_name': 'NMOS_3T_X1_Y2', - 'bbox': [0, 0, 6480, 12600], - 'terminals': [], - 'constraints': place_on_grid - }, - { - 'abstract_template_name': 'NMOS_3T', - 'concrete_template_name': 'NMOS_3T_X2_Y1', - 'bbox': [0, 0, 10800, 6300], - 'terminals': [], - 'constraints': place_on_grid - }, - { - 'abstract_template_name': 'PMOS_3T', - 'concrete_template_name': 'PMOS_3T_X1_Y2', - 'bbox': [0, 0, 6480, 12600], - 'terminals': [], - 'constraints': place_on_grid - }, - { - 'abstract_template_name': 'PMOS_3T', - 'concrete_template_name': 'PMOS_3T_X2_Y1', - 'bbox': [0, 0, 10800, 6300], - 'terminals': [], - 'constraints': place_on_grid - } ] } return data From 11ab4ce9415945babea0ad3808365fcf82ceb90f Mon Sep 17 00:00:00 2001 From: soneryaldiz Date: Tue, 8 Nov 2022 15:16:32 -0800 Subject: [PATCH 04/19] rename pythonic to python --- align/cmdline.py | 2 +- align/pnr/placer_pythonic_sp.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/align/cmdline.py b/align/cmdline.py index 4c14ed8326..90189ed326 100644 --- a/align/cmdline.py +++ b/align/cmdline.py @@ -162,7 +162,7 @@ def __init__(self, *args, **kwargs): help="Runtime limit in seconds for ILP in each iteration of placement") parser.add_argument('--placer', - choices=['pythonic', 'cpp'], + choices=['cpp', 'python'], default='cpp', help='Select the placer engine to use. Default: %(default)s') diff --git a/align/pnr/placer_pythonic_sp.py b/align/pnr/placer_pythonic_sp.py index 48c2a8f3db..6607afdc16 100644 --- a/align/pnr/placer_pythonic_sp.py +++ b/align/pnr/placer_pythonic_sp.py @@ -11,7 +11,7 @@ from align.pnr.grid_constraints import gen_constraints_for_module -class HyperParameters(): +class HyperParameters: max_sequence_pairs = 100 max_block_variants = 100 max_candidates = 10000 @@ -365,6 +365,7 @@ def place_using_sequence_pairs(placement_data, module, top_level): solutions.sort(key=lambda x: x['cost']) max_solutions = hyper_params.max_solutions if module['name'] == top_level else 1 + for i in range(max_solutions): solution = solutions[i] new_module = copy.deepcopy(module) From 0682472f2c26205d38dc9aca42c4377c8edfadbe Mon Sep 17 00:00:00 2001 From: soneryaldiz Date: Tue, 8 Nov 2022 18:17:31 -0800 Subject: [PATCH 05/19] propagate global_signals down --- align/pnr/placer_pythonic.py | 20 ++++++++++ align/pnr/placer_pythonic_sp.py | 18 +++------ tests/placer_pythonic/test_placer_pythonic.py | 40 +++++++++++++++---- .../test_placer_pythonic_sp.py | 10 ++--- 4 files changed, 63 insertions(+), 25 deletions(-) diff --git a/align/pnr/placer_pythonic.py b/align/pnr/placer_pythonic.py index c4ba719b3d..1ba019f4f5 100644 --- a/align/pnr/placer_pythonic.py +++ b/align/pnr/placer_pythonic.py @@ -43,6 +43,22 @@ def trim_placement_data(placement_data, top_level): return new_placement_data +def propagate_down_global_signals(modules: dict, module_name: str, global_signals: list): + GS = 'global_signals' + modules[module_name][GS] = modules[module_name].get(GS, list()) + global_signals + for instance in modules[module_name]['instances']: + sub_module_name = instance['abstract_template_name'] + if sub_module_name in modules: + signals_to_propagate = list() + for formal_actual in instance['fa_map']: + formal = formal_actual['formal'] + actual = formal_actual['actual'] + if actual in global_signals and formal not in modules[sub_module_name].get(GS, list()): + signals_to_propagate.append(formal) + if signals_to_propagate: + propagate_down_global_signals(modules, sub_module_name, signals_to_propagate) + + def pythonic_placer(top_level, input_data, scale_factor=1): placement_data = dict() @@ -59,6 +75,10 @@ def pythonic_placer(top_level, input_data, scale_factor=1): modules = {module['name']: module for module in input_data['modules']} topological_order, found_modules, _ = compute_topoorder(modules, top_level) + # Propagate power pins down the modules + if global_signals := [x['actual'] for x in input_data['global_signals']]: + propagate_down_global_signals(modules, top_level, global_signals) + for name in topological_order: if name not in found_modules: continue diff --git a/align/pnr/placer_pythonic_sp.py b/align/pnr/placer_pythonic_sp.py index 6607afdc16..3513d3481a 100644 --- a/align/pnr/placer_pythonic_sp.py +++ b/align/pnr/placer_pythonic_sp.py @@ -93,7 +93,7 @@ def enumerate_sequence_pairs(constraints, instance_map: dict, max_sequences: int return sequence_pairs -def enumerate_variants(constraints, instance_map: dict, variant_counts: dict, max_variants: int): +def enumerate_block_variants(constraints, instance_map: dict, variant_counts: dict, max_variants: int): # Group instances that should use the same concrete template groups = list() @@ -334,19 +334,13 @@ def place_using_sequence_pairs(placement_data, module, top_level): constraints = verilog_json['modules'][0]['constraints'] sequence_pairs = enumerate_sequence_pairs(constraints, instance_map, hyper_params.max_sequence_pairs) - variants = enumerate_variants(constraints, instance_map, variant_counts, hyper_params.max_block_variants) + block_variants = enumerate_block_variants(constraints, instance_map, variant_counts, hyper_params.max_block_variants) solutions = list() - cnt = 0 - for sequence_pair, variant_selection in itertools.product(sequence_pairs, variants): - if cnt > hyper_params.max_candidates: - break - cnt += 1 - print(f'{sequence_pair=}, {variant_selection=}') - + for block_variant, sequence_pair in itertools.islice(itertools.product(block_variants, sequence_pairs), hyper_params.max_candidates): instance_sizes = dict() place_on_grid = dict() - for idx, selected in enumerate(variant_selection): + for idx, selected in enumerate(block_variant): instance_name = reverse_instance_map[idx] concrete_template = variant_map[instances[instance_name][ATN]][selected] bbox = concrete_template['bbox'] @@ -356,7 +350,7 @@ def place_using_sequence_pairs(placement_data, module, top_level): solution = place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair, place_on_grid=place_on_grid) if solution: solution['sequence_pair'] = sequence_pair - solution['variant_selection'] = variant_selection + solution['block_variant'] = block_variant solutions.append(solution) assert solutions, f'No placement solution found for {module}' @@ -372,7 +366,7 @@ def place_using_sequence_pairs(placement_data, module, top_level): for instance in new_module['instances']: instance_name = instance['instance_name'] instance['transformation'] = solution['transformations'][instance_name] - variant_index = solution['variant_selection'][instance_map[instance_name]] + variant_index = solution['block_variant'][instance_map[instance_name]] instance[CTN] = variant_map[instance[ATN]][variant_index]['concrete_name'] check_place_on_grid(instance, variant_map[instance[ATN]][variant_index]['constraints']) diff --git a/tests/placer_pythonic/test_placer_pythonic.py b/tests/placer_pythonic/test_placer_pythonic.py index 3985a45195..70172a0144 100644 --- a/tests/placer_pythonic/test_placer_pythonic.py +++ b/tests/placer_pythonic/test_placer_pythonic.py @@ -1,5 +1,5 @@ import json -from align.pnr.placer_pythonic import pythonic_placer +from align.pnr.placer_pythonic import pythonic_placer, propagate_down_global_signals def ring_oscillator(): @@ -41,7 +41,7 @@ def ring_oscillator(): 'modules': [ { 'name': 'ring_oscillator_stage', - 'parameters': ['VI', 'VO', 'VCCX', 'VSSX'], + 'parameters': ['VI', 'VO', 'VCC', 'VSSX'], 'constraints': [ {'constraint': 'Order', 'instances': ['X_MP0', 'X_MN0'], 'direction': 'top_to_bottom'} ], @@ -52,7 +52,7 @@ def ring_oscillator(): }, { 'instance_name': 'X_MP0', 'abstract_template_name': 'PMOS_3T', - 'fa_map': [{'formal': 'D', 'actual': 'VO'}, {'formal': 'G', 'actual': 'VI'}, {'formal': 'S', 'actual': 'VCCX'}] + 'fa_map': [{'formal': 'D', 'actual': 'VO'}, {'formal': 'G', 'actual': 'VI'}, {'formal': 'S', 'actual': 'VCC'}] } ] }, @@ -66,31 +66,43 @@ def ring_oscillator(): 'instances': [ { 'instance_name': 'XI1', 'abstract_template_name': 'ring_oscillator_stage', - 'fa_map': [{'formal': 'VCCX', 'actual': 'VCCX'}, {'formal': 'VSSX', 'actual': 'VSSX'}, + 'fa_map': [{'formal': 'VCC', 'actual': 'VCCX'}, {'formal': 'VSSX', 'actual': 'VSSX'}, {'formal': 'VI', 'actual': 'VO'}, {'formal': 'VO', 'actual': 'V1'}] }, { 'instance_name': 'XI2', 'abstract_template_name': 'ring_oscillator_stage', - 'fa_map': [{'formal': 'VCCX', 'actual': 'VCCX'}, {'formal': 'VSSX', 'actual': 'VSSX'}, + 'fa_map': [{'formal': 'VCC', 'actual': 'VCCX'}, {'formal': 'VSSX', 'actual': 'VSSX'}, {'formal': 'VI', 'actual': 'V1'}, {'formal': 'VO', 'actual': 'V2'}] }, { 'instance_name': 'XI3', 'abstract_template_name': 'ring_oscillator_stage', - 'fa_map': [{'formal': 'VCCX', 'actual': 'VCCX'}, {'formal': 'VSSX', 'actual': 'VSSX'}, + 'fa_map': [{'formal': 'VCC', 'actual': 'VCCX'}, {'formal': 'VSSX', 'actual': 'VSSX'}, {'formal': 'VI', 'actual': 'V2'}, {'formal': 'VO', 'actual': 'V3'}] }, { 'instance_name': 'XI4', 'abstract_template_name': 'ring_oscillator_stage', - 'fa_map': [{'formal': 'VCCX', 'actual': 'VCCX'}, {'formal': 'VSSX', 'actual': 'VSSX'}, + 'fa_map': [{'formal': 'VCC', 'actual': 'VCCX'}, {'formal': 'VSSX', 'actual': 'VSSX'}, {'formal': 'VI', 'actual': 'V3'}, {'formal': 'VO', 'actual': 'V4'}] }, { 'instance_name': 'XI5', 'abstract_template_name': 'ring_oscillator_stage', - 'fa_map': [{'formal': 'VCCX', 'actual': 'VCCX'}, {'formal': 'VSSX', 'actual': 'VSSX'}, + 'fa_map': [{'formal': 'VCC', 'actual': 'VCCX'}, {'formal': 'VSSX', 'actual': 'VSSX'}, {'formal': 'VI', 'actual': 'V4'}, {'formal': 'VO', 'actual': 'VO'}] } ] } + ], + 'global_signals': [ + { + "prefix": "global_power", + "formal": "supply0", + "actual": "VSSX" + }, + { + "prefix": "global_power", + "formal": "supply1", + "actual": "VCCX" + } ] } return data @@ -112,3 +124,15 @@ def test_place_ring_oscillator_stage(): placement_data = pythonic_placer('ring_oscillator_stage', input_data) assert len(placement_data['leaves']) == 2 assert len(placement_data['modules']) == 1 + + +def test_propagate_down_global_signals(): + input_data = ring_oscillator() + modules = {module['name']: module for module in input_data['modules']} + global_signals = [x['actual'] for x in input_data['global_signals']] + propagate_down_global_signals(modules, 'ring_oscillator', global_signals) + assert set(modules['ring_oscillator_stage']['global_signals']) == {'VCC', 'VSSX'} + + +# TODO: compute_topoorder +# TODO: trim_placement_data diff --git a/tests/placer_pythonic/test_placer_pythonic_sp.py b/tests/placer_pythonic/test_placer_pythonic_sp.py index 7d43a71cc3..36f53eecce 100644 --- a/tests/placer_pythonic/test_placer_pythonic_sp.py +++ b/tests/placer_pythonic/test_placer_pythonic_sp.py @@ -7,7 +7,7 @@ import align.schema.constraint as constraint_schema from align.schema.types import set_context from align.schema import Model, Instance, SubCircuit, Library -from align.pnr.placer_pythonic_sp import enumerate_sequence_pairs, enumerate_variants, place_sequence_pair +from align.pnr.placer_pythonic_sp import enumerate_sequence_pairs, enumerate_block_variants, place_sequence_pair DRAW = False @@ -102,18 +102,18 @@ def test_enumerate_sequence_pairs(): assert sequence_pairs[0] == ((0, 1, 2, 3), (3, 2, 1, 0)) -def test_enumerate_variants(): +def test_enumerate_block_variants(): constraints, instance_map = initialize_constraints(2) variant_counts = {"M0": 4, "M1": 5} - variants = enumerate_variants(constraints, instance_map, variant_counts, 100) + variants = enumerate_block_variants(constraints, instance_map, variant_counts, 100) assert len(variants) == 20 constraints, instance_map = initialize_constraints(4) variant_counts = {k: 2 for k in instance_map.keys()} with set_context(constraints): constraints.append(constraint_schema.SameTemplate(instances=[f'M{i}' for i in range(4)])) - variants = enumerate_variants(constraints, instance_map, variant_counts, 100) + variants = enumerate_block_variants(constraints, instance_map, variant_counts, 100) assert len(variants) == 2 assert variants == [tuple([0]*4), tuple([1]*4)] @@ -122,7 +122,7 @@ def test_enumerate_variants(): with set_context(constraints): constraints.append(constraint_schema.SameTemplate(instances=["M1", "M2"])) constraints.append(constraint_schema.SameTemplate(instances=["M2", "M3"])) - variants = enumerate_variants(constraints, instance_map, variant_counts, 100) + variants = enumerate_block_variants(constraints, instance_map, variant_counts, 100) assert len(variants) == 3*3 From 428666022ad9ab909b0dadd75574d9b927f98b92 Mon Sep 17 00:00:00 2001 From: soneryaldiz Date: Wed, 9 Nov 2022 11:37:23 -0800 Subject: [PATCH 06/19] optimize HPWL; plot placement --- align/pnr/placer_pythonic.py | 23 ++++- align/pnr/placer_pythonic_sp.py | 46 ++++++++-- tests/placer_pythonic/test_placer_pythonic.py | 86 ++++++++++++++++++- .../test_placer_pythonic_sp.py | 12 +-- 4 files changed, 149 insertions(+), 18 deletions(-) diff --git a/align/pnr/placer_pythonic.py b/align/pnr/placer_pythonic.py index 1ba019f4f5..eb13afaa6a 100644 --- a/align/pnr/placer_pythonic.py +++ b/align/pnr/placer_pythonic.py @@ -1,3 +1,5 @@ +import logging + from align.schema.hacks import VerilogJsonTop from align.pnr.checker import check_placement from align.pnr.placer_pythonic_sp import place_using_sequence_pairs @@ -40,6 +42,11 @@ def trim_placement_data(placement_data, top_level): new_placement_data['leaves'] = [leaf for leaf in placement_data['leaves'] if leaf['concrete_name'] in all_modules_leaves] new_placement_data['modules'] = [leaf for leaf in placement_data['modules'] if leaf['concrete_name'] in all_modules_leaves] + for key in ['leaves', 'modules']: + for elem in new_placement_data[key]: + if 'pin_bbox' in elem: + del elem['pin_bbox'] + return new_placement_data @@ -65,12 +72,26 @@ def pythonic_placer(top_level, input_data, scale_factor=1): placement_data['modules'] = list() placement_data['leaves'] = list() for leaf in input_data['leaves']: + # Calculate a bounding box for each pin for HPWL calculation + pin_bbox = dict() + for term in leaf['terminals']: + if term['netType'] == 'pin': + net_name = term['netName'] + if net_name not in pin_bbox: + pin_bbox[net_name] = [x for x in term['rect']] + else: + pin_bbox[net_name][0] = min(pin_bbox[net_name][0], term['rect'][0]) + pin_bbox[net_name][1] = min(pin_bbox[net_name][1], term['rect'][1]) + pin_bbox[net_name][2] = max(pin_bbox[net_name][2], term['rect'][2]) + pin_bbox[net_name][3] = max(pin_bbox[net_name][3], term['rect'][3]) + placement_data['leaves'].append({ 'abstract_name': leaf['abstract_template_name'], 'concrete_name': leaf['concrete_template_name'], 'constraints': leaf['constraints'], 'bbox': leaf['bbox'], - 'terminals': leaf['terminals']}) + 'terminals': leaf['terminals'], + 'pin_bbox': pin_bbox}) modules = {module['name']: module for module in input_data['modules']} topological_order, found_modules, _ = compute_topoorder(modules, top_level) diff --git a/align/pnr/placer_pythonic_sp.py b/align/pnr/placer_pythonic_sp.py index 3513d3481a..d80fafdbf1 100644 --- a/align/pnr/placer_pythonic_sp.py +++ b/align/pnr/placer_pythonic_sp.py @@ -9,6 +9,7 @@ import align.schema.constraint as constraint_schema from align.schema.hacks import VerilogJsonTop from align.pnr.grid_constraints import gen_constraints_for_module +from align.cell_fabric.transformation import Transformation, Rect class HyperParameters: @@ -197,11 +198,11 @@ def place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair # Half perimeter wire length model.add_var(name='HPWL', lb=0, ub=upper_bound) if wires: - for wire_name, terminals in wires: + for wire_name, instance_bbox in wires.items(): for tag, axis in itertools.product(['ll', 'ur'], ['x', 'y']): model.add_var(name=f'{wire_name}_{tag}{axis}') - for instance, bbox in terminals: + for instance, bbox in instance_bbox: size = dict(zip("xy", instance_sizes[instance])) for (tag, axis), offset in zip(itertools.product(['ll', 'ur'], ['x', 'y']), bbox): eqn = model.var_by_name(f'{instance}_ll{axis}') + offset + (size[axis] - 2*offset) * model.var_by_name(f'{instance}_f{axis}') @@ -209,8 +210,8 @@ def place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair model += model.var_by_name(f'{wire_name}_ll{axis}') <= eqn model += \ - mip.xsum(model.var_by_name(f'{wire_name}_ur{axis}') for wire_name, _ in wires for axis in ['x', 'y']) - \ - mip.xsum(model.var_by_name(f'{wire_name}_ll{axis}') for wire_name, _ in wires for axis in ['x', 'y']) == model.var_by_name('HPWL') + mip.xsum(model.var_by_name(f'{wire_name}_ur{axis}') for wire_name in wires for axis in ['x', 'y']) - \ + mip.xsum(model.var_by_name(f'{wire_name}_ll{axis}') for wire_name in wires for axis in ['x', 'y']) == model.var_by_name('HPWL') else: model += model.var_by_name('HPWL') == 0 @@ -340,6 +341,7 @@ def place_using_sequence_pairs(placement_data, module, top_level): for block_variant, sequence_pair in itertools.islice(itertools.product(block_variants, sequence_pairs), hyper_params.max_candidates): instance_sizes = dict() place_on_grid = dict() + wires = defaultdict(list) for idx, selected in enumerate(block_variant): instance_name = reverse_instance_map[idx] concrete_template = variant_map[instances[instance_name][ATN]][selected] @@ -347,7 +349,12 @@ def place_using_sequence_pairs(placement_data, module, top_level): instance_sizes[instance_name] = [bbox[2] - bbox[0], bbox[3] - bbox[1]] place_on_grid[instance_name] = [c for c in concrete_template.get('constraints', list()) if c['constraint'] == 'PlaceOnGrid'] - solution = place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair, place_on_grid=place_on_grid) + for formal_actual in instances[instance_name]['fa_map']: + formal, actual = formal_actual['formal'], formal_actual['actual'] + if 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) if solution: solution['sequence_pair'] = sequence_pair solution['block_variant'] = block_variant @@ -363,16 +370,41 @@ def place_using_sequence_pairs(placement_data, module, top_level): for i in range(max_solutions): solution = solutions[i] new_module = copy.deepcopy(module) + pin_bbox = dict() for instance in new_module['instances']: instance_name = instance['instance_name'] instance['transformation'] = solution['transformations'][instance_name] variant_index = solution['block_variant'][instance_map[instance_name]] - instance[CTN] = variant_map[instance[ATN]][variant_index]['concrete_name'] - check_place_on_grid(instance, variant_map[instance[ATN]][variant_index]['constraints']) + concrete_template = variant_map[instance[ATN]][variant_index] + instance[CTN] = concrete_template['concrete_name'] + check_place_on_grid(instance, concrete_template['constraints']) + + # Annotate bounding box of pins to the module + for formal_actual in instance['fa_map']: + formal, actual = formal_actual['formal'], formal_actual['actual'] + if actual not in module['global_signals']: + rect = concrete_template['pin_bbox'][formal] + if not rect: + continue + rect = Transformation( + instance['transformation']['oX'], + instance['transformation']['oY'], + instance['transformation']['sX'], + instance['transformation']['sY'] + ).hitRect(Rect(*rect)).canonical().toList() + + if actual not in pin_bbox: + pin_bbox[actual] = [x for x in rect] + else: + pin_bbox[actual][0] = min(pin_bbox[actual][0], rect[0]) + pin_bbox[actual][1] = min(pin_bbox[actual][1], rect[1]) + pin_bbox[actual][2] = max(pin_bbox[actual][2], rect[2]) + pin_bbox[actual][3] = max(pin_bbox[actual][3], rect[3]) new_module['bbox'] = [0, 0, solution['width'], solution['height']] new_module['abstract_name'] = new_module['name'] new_module['concrete_name'] = new_module['name'] + f'_{i}' + new_module['pin_bbox'] = pin_bbox del new_module['name'] placement_data['modules'].append(new_module) diff --git a/tests/placer_pythonic/test_placer_pythonic.py b/tests/placer_pythonic/test_placer_pythonic.py index 70172a0144..334e13bddf 100644 --- a/tests/placer_pythonic/test_placer_pythonic.py +++ b/tests/placer_pythonic/test_placer_pythonic.py @@ -1,5 +1,11 @@ import json +import plotly.graph_objects as go +import plotly.express as px + from align.pnr.placer_pythonic import pythonic_placer, propagate_down_global_signals +from align.cell_fabric.transformation import Transformation, Rect + +DRAW = False def ring_oscillator(): @@ -13,28 +19,44 @@ def ring_oscillator(): 'abstract_template_name': 'NMOS_3T', 'concrete_template_name': 'NMOS_3T_X1_Y2', 'bbox': [0, 0, 6480, 12600], - 'terminals': [], + 'terminals': [ + {'layer': 'M1', 'netName': 'D', 'rect': [2000, 1000, 2500, 11000], 'netType': 'pin'}, + {'layer': 'M1', 'netName': 'G', 'rect': [3000, 1000, 3500, 11000], 'netType': 'pin'}, + {'layer': 'M1', 'netName': 'S', 'rect': [4000, 1000, 4500, 110000], 'netType': 'pin'} + ], 'constraints': place_on_grid }, { 'abstract_template_name': 'NMOS_3T', 'concrete_template_name': 'NMOS_3T_X2_Y1', 'bbox': [0, 0, 10800, 6300], - 'terminals': [], + 'terminals': [ + {'layer': 'M2', 'netName': 'D', 'rect': [1000, 1000, 9000, 1500], 'netType': 'pin'}, + {'layer': 'M2', 'netName': 'G', 'rect': [1000, 3000, 9000, 3500], 'netType': 'pin'}, + {'layer': 'M2', 'netName': 'S', 'rect': [1000, 5000, 9000, 5500], 'netType': 'pin'} + ], 'constraints': place_on_grid }, { 'abstract_template_name': 'PMOS_3T', 'concrete_template_name': 'PMOS_3T_X1_Y2', 'bbox': [0, 0, 6480, 12600], - 'terminals': [], + 'terminals': [ + {'layer': 'M1', 'netName': 'D', 'rect': [2000, 1000, 2500, 10000], 'netType': 'pin'}, + {'layer': 'M1', 'netName': 'G', 'rect': [3000, 1000, 3500, 10000], 'netType': 'pin'}, + {'layer': 'M1', 'netName': 'S', 'rect': [4000, 1000, 4500, 10000], 'netType': 'pin'} + ], 'constraints': place_on_grid }, { 'abstract_template_name': 'PMOS_3T', 'concrete_template_name': 'PMOS_3T_X2_Y1', 'bbox': [0, 0, 10800, 6300], - 'terminals': [], + 'terminals': [ + {'layer': 'M2', 'netName': 'D', 'rect': [1000, 1000, 9000, 1500], 'netType': 'pin'}, + {'layer': 'M2', 'netName': 'G', 'rect': [1000, 3000, 9000, 3500], 'netType': 'pin'}, + {'layer': 'M2', 'netName': 'S', 'rect': [1000, 5000, 9000, 5500], 'netType': 'pin'} + ], 'constraints': place_on_grid } ], @@ -108,6 +130,58 @@ def ring_oscillator(): return data +def draw_placement(placement_data, module_name): + + leaves = {x['concrete_name']: x for x in placement_data['leaves']} + modules = {x['concrete_name']: x for x in placement_data['modules']} + module = modules[module_name] + + colorscale = px.colors.qualitative.Alphabet + + fig = go.Figure() + # fig.update_xaxes(range=[0, model.var_by_name('W').x+1]) + # fig.update_yaxes(range=[0, model.var_by_name('H').x+1]) + + # Add shapes + x_lst = list() + y_lst = list() + n_lst = list() + + i = 0 + for instance in module['instances']: + concrete_name = instance['concrete_template_name'] + + if concrete_name in leaves: + bbox = leaves[concrete_name]['bbox'] + elif concrete_name in modules: + bbox = modules[concrete_name]['bbox'] + else: + assert False + + bbox = Transformation( + instance['transformation']['oX'], + instance['transformation']['oY'], + instance['transformation']['sX'], + instance['transformation']['sY'] + ).hitRect(Rect(*bbox)).canonical().toList() + + llx, lly, urx, ury = bbox + + color = colorscale[i % len(colorscale)] + fig.add_shape(type="rect", x0=llx, y0=lly, x1=urx, y1=ury, line=dict(color="RoyalBlue", width=3), fillcolor=color) + i += 1 + x_lst.append((llx+urx)/2) + y_lst.append((lly+ury)/2) + n_lst.append(f"{instance['instance_name']}") + + fig.update_shapes(opacity=0.5, xref="x", yref="y") + + # Add labels + fig.add_trace(go.Scatter(x=x_lst, y=y_lst, text=n_lst, mode="text", textfont=dict(color="black", size=24))) + + fig.show() + + def test_place_ring_oscillator(): input_data = ring_oscillator() with open('placement_input.json', "wt") as fp: @@ -117,6 +191,8 @@ def test_place_ring_oscillator(): assert len(placement_data['modules']) == 2 with open('placement_output.json', "wt") as fp: json.dump(placement_data, fp=fp, indent=2) + if DRAW: + draw_placement(placement_data, 'ring_oscillator_0') def test_place_ring_oscillator_stage(): @@ -124,6 +200,8 @@ def test_place_ring_oscillator_stage(): placement_data = pythonic_placer('ring_oscillator_stage', input_data) assert len(placement_data['leaves']) == 2 assert len(placement_data['modules']) == 1 + if DRAW: + draw_placement(placement_data, 'ring_oscillator_stage_0') def test_propagate_down_global_signals(): diff --git a/tests/placer_pythonic/test_placer_pythonic_sp.py b/tests/placer_pythonic/test_placer_pythonic_sp.py index 36f53eecce..d3ef177fc1 100644 --- a/tests/placer_pythonic/test_placer_pythonic_sp.py +++ b/tests/placer_pythonic/test_placer_pythonic_sp.py @@ -35,9 +35,9 @@ def draw(model, instance_map, instance_sizes, wires): n_lst.append(f"${name}$") i = 0 - for name, terminals in wires: + for name, instance_bbox in wires.items(): color = colorscale[i % len(colorscale)] - for instance, bbox in terminals: + for instance, bbox in instance_bbox: size = dict(zip("xy", instance_sizes[instance])) bb = dict() for (tag, axis), offset in zip(itertools.product(['ll', 'ur'], ['x', 'y']), bbox): @@ -135,14 +135,14 @@ def test_place_sequence_pair_1(): {"constraint": "place_on_grid", "direction": "H", "pitch": 2, "ored_terms": [{'offsets': [0], 'scalings': [-1, 1]}]}, {"constraint": "place_on_grid", "direction": "V", "pitch": 2, "ored_terms": [{'offsets': [0], 'scalings': [-1, 1]}]} ] - wires = [ - ('net1', [('M0', (1, 2, 2, 2.25)), ('M1', (3, 1, 4, 1.25))]) - ] + wires = { + 'net1': [('M0', (1, 2, 2, 2.25)), ('M1', (3, 1, 4, 1.25))] + } place_on_grid = {f"M{i}": c for i in range(n)} solution = place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair, wires=wires, place_on_grid=place_on_grid) assert solution['transformations']['M0'] == {'oX': 10, 'oY': 0, 'sX': -1, 'sY': 1} assert solution['transformations']['M1'] == {'oX': 14, 'oY': 4, 'sX': -1, 'sY': -1} - if DRAW: + if True: draw(solution['model'], instance_map, instance_sizes, wires) From f22d17c5235660e5305c5f11bfe769a35cf44690 Mon Sep 17 00:00:00 2001 From: soneryaldiz Date: Wed, 9 Nov 2022 11:49:46 -0800 Subject: [PATCH 07/19] netPriority; normalize HPWL in objective --- align/pnr/placer_pythonic_sp.py | 60 +++++++++++-------- .../test_placer_pythonic_sp.py | 8 +-- 2 files changed, 40 insertions(+), 28 deletions(-) diff --git a/align/pnr/placer_pythonic_sp.py b/align/pnr/placer_pythonic_sp.py index d80fafdbf1..8858d6d557 100644 --- a/align/pnr/placer_pythonic_sp.py +++ b/align/pnr/placer_pythonic_sp.py @@ -195,27 +195,6 @@ def place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair origin = grid*pitch + mip.xsum(v[0]*v[1] for v in offset_variables) model += origin - size[axis] * flip == model.var_by_name(f'{name}_ll{axis}') - # Half perimeter wire length - model.add_var(name='HPWL', lb=0, ub=upper_bound) - if wires: - for wire_name, instance_bbox in wires.items(): - for tag, axis in itertools.product(['ll', 'ur'], ['x', 'y']): - model.add_var(name=f'{wire_name}_{tag}{axis}') - - for instance, bbox in instance_bbox: - size = dict(zip("xy", instance_sizes[instance])) - for (tag, axis), offset in zip(itertools.product(['ll', 'ur'], ['x', 'y']), bbox): - eqn = model.var_by_name(f'{instance}_ll{axis}') + offset + (size[axis] - 2*offset) * model.var_by_name(f'{instance}_f{axis}') - model += eqn <= model.var_by_name(f'{wire_name}_ur{axis}') - model += model.var_by_name(f'{wire_name}_ll{axis}') <= eqn - - model += \ - mip.xsum(model.var_by_name(f'{wire_name}_ur{axis}') for wire_name in wires for axis in ['x', 'y']) - \ - mip.xsum(model.var_by_name(f'{wire_name}_ll{axis}') for wire_name in wires for axis in ['x', 'y']) == model.var_by_name('HPWL') - - else: - model += model.var_by_name('HPWL') == 0 - # Constraints implied by the sequence pairs reverse_map = {v: k for k, v in instance_map.items()} instance_pos = {reverse_map[index]: i for i, index in enumerate(sequence_pair[0])} @@ -236,6 +215,7 @@ def place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair assert False # Placement constraints + net_priority = dict() for constraint in constraints: if isinstance(constraint, constraint_schema.Boundary): @@ -254,10 +234,42 @@ def place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair for name in constraint.instances_on(['west', 'northwest', 'southwest']): model += model.var_by_name(f'{name}_llx') == 0 + if isinstance(constraint, constraint_schema.NetPriority): + nets = getattr(constraint, 'nets') + weight = getattr(constraint, 'weight') + for net in nets: + net_priority[net] = weight + # TODO: Placement constraints except Order (sequence pair already satisfies order) - # Minimize the perimeter of the bounding box - model.objective += model.var_by_name('W') + model.var_by_name('H') + model.var_by_name('HPWL') + # Half perimeter wire length + model.add_var(name='HPWL', lb=0, ub=upper_bound) + if wires: + for wire_name, instance_bbox in wires.items(): + for tag, axis in itertools.product(['ll', 'ur'], ['x', 'y']): + model.add_var(name=f'{wire_name}_{tag}{axis}') + + for instance, bbox in instance_bbox: + size = dict(zip("xy", instance_sizes[instance])) + for (tag, axis), offset in zip(itertools.product(['ll', 'ur'], ['x', 'y']), bbox): + eqn = model.var_by_name(f'{instance}_ll{axis}') + offset + (size[axis] - 2*offset) * model.var_by_name(f'{instance}_f{axis}') + model += eqn <= model.var_by_name(f'{wire_name}_ur{axis}') + model += model.var_by_name(f'{wire_name}_ll{axis}') <= eqn + + model += \ + mip.xsum(net_priority.get(wire_name, 1) * model.var_by_name(f'{wire_name}_ur{axis}') for wire_name in wires for axis in ['x', 'y']) - \ + mip.xsum(net_priority.get(wire_name, 1) * model.var_by_name(f'{wire_name}_ll{axis}') for wire_name in wires for axis in ['x', 'y']) == \ + model.var_by_name('HPWL') + + else: + model += model.var_by_name('HPWL') == 0 + + # TODO: Normalize the HPWL by number of nets + + # Minimize the perimeter of the bounding box and normalized HPWL + scale_hpwl = 1/len(wires) if wires else 1 + + model.objective += model.var_by_name('W') + model.var_by_name('H') + scale_hpwl * model.var_by_name('HPWL') model.write('model.lp') @@ -295,7 +307,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, + 'hpwl': model.var_by_name('HPWL').x / scale_hpwl, 'transformations': transformations, 'model': model } diff --git a/tests/placer_pythonic/test_placer_pythonic_sp.py b/tests/placer_pythonic/test_placer_pythonic_sp.py index d3ef177fc1..3b8fe7364c 100644 --- a/tests/placer_pythonic/test_placer_pythonic_sp.py +++ b/tests/placer_pythonic/test_placer_pythonic_sp.py @@ -32,7 +32,7 @@ def draw(model, instance_map, instance_sizes, wires): fig.add_shape(type="rect", x0=llx, y0=lly, x1=urx, y1=ury, line=dict(color="RoyalBlue", width=3), fillcolor=color) x_lst.append((llx+urx)/2) y_lst.append((lly+ury)/2) - n_lst.append(f"${name}$") + n_lst.append(f"{name}") i = 0 for name, instance_bbox in wires.items(): @@ -48,13 +48,13 @@ def draw(model, instance_map, instance_sizes, wires): fig.add_shape(type="rect", x0=llx, y0=lly, x1=urx, y1=ury, line=dict(color="Black", width=1), fillcolor=color) x_lst.append((llx+urx)/2) y_lst.append((lly+ury)/2) - n_lst.append(f"${name}$") + n_lst.append(f"{name}") i += 1 fig.update_shapes(opacity=0.5, xref="x", yref="y") # Add labels - fig.add_trace(go.Scatter(x=x_lst, y=y_lst, text=n_lst, mode="text", textfont=dict(color="black", size=196))) + fig.add_trace(go.Scatter(x=x_lst, y=y_lst, text=n_lst, mode="text", textfont=dict(color="black", size=24))) fig.show() @@ -142,7 +142,7 @@ def test_place_sequence_pair_1(): solution = place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair, wires=wires, place_on_grid=place_on_grid) assert solution['transformations']['M0'] == {'oX': 10, 'oY': 0, 'sX': -1, 'sY': 1} assert solution['transformations']['M1'] == {'oX': 14, 'oY': 4, 'sX': -1, 'sY': -1} - if True: + if DRAW: draw(solution['model'], instance_map, instance_sizes, wires) From 38a15c2c40f039a351db7c19bea1cb04b97ed0eb Mon Sep 17 00:00:00 2001 From: soneryaldiz Date: Wed, 9 Nov 2022 12:01:49 -0800 Subject: [PATCH 08/19] adjust bbox in plot --- tests/placer_pythonic/test_placer_pythonic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/placer_pythonic/test_placer_pythonic.py b/tests/placer_pythonic/test_placer_pythonic.py index 334e13bddf..03138eff60 100644 --- a/tests/placer_pythonic/test_placer_pythonic.py +++ b/tests/placer_pythonic/test_placer_pythonic.py @@ -139,8 +139,8 @@ def draw_placement(placement_data, module_name): colorscale = px.colors.qualitative.Alphabet fig = go.Figure() - # fig.update_xaxes(range=[0, model.var_by_name('W').x+1]) - # fig.update_yaxes(range=[0, model.var_by_name('H').x+1]) + fig.update_xaxes(range=[0, max(module['bbox'])]) + fig.update_yaxes(range=[0, max(module['bbox'])]) # Add shapes x_lst = list() From cdf744c692e2a53a06b5c1ec8567b548d0c43447 Mon Sep 17 00:00:00 2001 From: soneryaldiz Date: Wed, 9 Nov 2022 16:00:31 -0800 Subject: [PATCH 09/19] satisfy Align,SymmetricBlocks,Spread --- align/pnr/placer_pythonic.py | 1 + align/pnr/placer_pythonic_sp.py | 50 +++++++++++++++---- align/schema/constraint.py | 2 +- tests/placer_pythonic/test_placer_pythonic.py | 26 +++++++--- .../test_placer_pythonic_sp.py | 49 +++++++++--------- 5 files changed, 87 insertions(+), 41 deletions(-) diff --git a/align/pnr/placer_pythonic.py b/align/pnr/placer_pythonic.py index eb13afaa6a..a5adefae3f 100644 --- a/align/pnr/placer_pythonic.py +++ b/align/pnr/placer_pythonic.py @@ -71,6 +71,7 @@ def pythonic_placer(top_level, input_data, scale_factor=1): placement_data = dict() placement_data['modules'] = list() placement_data['leaves'] = list() + placement_data['scale_factor'] = scale_factor for leaf in input_data['leaves']: # Calculate a bounding box for each pin for HPWL calculation pin_bbox = dict() diff --git a/align/pnr/placer_pythonic_sp.py b/align/pnr/placer_pythonic_sp.py index 8858d6d557..dad352b12e 100644 --- a/align/pnr/placer_pythonic_sp.py +++ b/align/pnr/placer_pythonic_sp.py @@ -138,7 +138,7 @@ def enumerate_block_variants(constraints, instance_map: dict, variant_counts: di return variants -def place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair, wires=None, place_on_grid=None): +def place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair, wires=None, place_on_grid=None, scale_factor=1): model = mip.Model(sense=mip.MINIMIZE, solver_name=mip.CBC) model.verbose = 0 # set to one to see more progress output with the solver @@ -214,9 +214,9 @@ def place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair else: assert False - # Placement constraints + # Parse constraints net_priority = dict() - for constraint in constraints: + for constraint in constraint_schema.expand_user_constraints(constraints): if isinstance(constraint, constraint_schema.Boundary): if max_width := getattr(constraint, 'max_width', False): @@ -224,7 +224,7 @@ def place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair if max_height := getattr(constraint, 'max_height', False): model += model.var_by_name('H') <= max_height - if isinstance(constraint, constraint_schema.PlaceOnBoundary): + elif isinstance(constraint, constraint_schema.PlaceOnBoundary): for name in constraint.instances_on(['north', 'northwest', 'northeast']): model += model.var_by_name(f'{name}_ury') == model.var_by_name('H') for name in constraint.instances_on(['south', 'southwest', 'southeast']): @@ -234,13 +234,45 @@ def place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair for name in constraint.instances_on(['west', 'northwest', 'southwest']): model += model.var_by_name(f'{name}_llx') == 0 - if isinstance(constraint, constraint_schema.NetPriority): + elif isinstance(constraint, constraint_schema.NetPriority): nets = getattr(constraint, 'nets') weight = getattr(constraint, 'weight') for net in nets: net_priority[net] = weight - # TODO: Placement constraints except Order (sequence pair already satisfies order) + elif isinstance(constraint, constraint_schema.Align): + instances = getattr(constraint, 'instances') + line = getattr(constraint, 'line') + supported_lines = {'h_bottom': 'lly', 'h_top': 'ury', 'v_left': 'llx', 'v_right': 'urx'} + assert line in supported_lines, f'{line} not supported. Please choose among {supported_lines.keys()}' + axis = supported_lines[line] + i0 = instances[0] + 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.SymmetricBlocks): + pairs = getattr(constraint, 'pairs') + axis = 'x' if getattr(constraint, 'direction') == 'V' else 'y' + symmetry_line = model.add_var(lb=0, ub=upper_bound) + for pair in pairs: + if len(pair) == 1: + rel_tol = 10 # max distance from symmetry line should be less than 1/10th the block width + model += (1-1/rel_tol)*(model.var_by_name(f'{pair[0]}_ll{axis}'), model.var_by_name(f'{pair[0]}_ur{axis}')) <= 2*symmetry_line + model += (1+1/rel_tol)*(model.var_by_name(f'{pair[0]}_ll{axis}'), model.var_by_name(f'{pair[0]}_ur{axis}')) >= 2*symmetry_line + else: + model += model.var_by_name(f'{pair[0]}_ll{axis}') + model.var_by_name(f'{pair[0]}_ur{axis}') + \ + model.var_by_name(f'{pair[1]}_ll{axis}') + model.var_by_name(f'{pair[1]}_ur{axis}') == \ + 4*symmetry_line + + elif isinstance(constraint, constraint_schema.Spread): + instances = getattr(constraint, 'instances') + distance = getattr(constraint, 'distance') * scale_factor + axis = 'x' if getattr(constraint, 'direction') == 'horizontal' else 'y' + # TODO: If the elements are already ordered in sequence pair, no need to introduce a binary variable! + for i0, i1 in itertools.combinations(instances, 2): + var = model.add_var(var_type=mip.BINARY) + model += distance - model.var_by_name(f'{i1}_ll{axis}') + model.var_by_name(f'{i0}_ur{axis}') - upper_bound*var <= 0 + model += distance - model.var_by_name(f'{i0}_ll{axis}') + model.var_by_name(f'{i1}_ur{axis}') - upper_bound*(1-var) <= 0 # Half perimeter wire length model.add_var(name='HPWL', lb=0, ub=upper_bound) @@ -264,8 +296,6 @@ def place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair else: model += model.var_by_name('HPWL') == 0 - # TODO: Normalize the HPWL by number of nets - # Minimize the perimeter of the bounding box and normalized HPWL scale_hpwl = 1/len(wires) if wires else 1 @@ -366,7 +396,9 @@ def place_using_sequence_pairs(placement_data, module, top_level): if 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) + solution = place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair, wires=wires, place_on_grid=place_on_grid, + scale_factor=placement_data['scale_factor']) + if solution: solution['sequence_pair'] = sequence_pair solution['block_variant'] = block_variant diff --git a/align/schema/constraint.py b/align/schema/constraint.py index 631f754e39..4cefb59a76 100644 --- a/align/schema/constraint.py +++ b/align/schema/constraint.py @@ -409,7 +409,7 @@ def cc(b1, b2, d='x'): ) bvars = solver.iter_bbox_vars(self.instances) - for b1, b2 in itertools.pairwise(bvars): + for b1, b2 in plain_itertools.combinations(bvars, 2): if self.direction == 'horizontal': yield cc(b1, b2, 'x') elif self.direction == 'vertical': diff --git a/tests/placer_pythonic/test_placer_pythonic.py b/tests/placer_pythonic/test_placer_pythonic.py index 03138eff60..601dc81ead 100644 --- a/tests/placer_pythonic/test_placer_pythonic.py +++ b/tests/placer_pythonic/test_placer_pythonic.py @@ -5,8 +5,6 @@ from align.pnr.placer_pythonic import pythonic_placer, propagate_down_global_signals from align.cell_fabric.transformation import Transformation, Rect -DRAW = False - def ring_oscillator(): place_on_grid = [ @@ -130,7 +128,12 @@ def ring_oscillator(): return data +DRAW = False + + def draw_placement(placement_data, module_name): + if not DRAW: + return leaves = {x['concrete_name']: x for x in placement_data['leaves']} modules = {x['concrete_name']: x for x in placement_data['modules']} @@ -191,8 +194,7 @@ def test_place_ring_oscillator(): assert len(placement_data['modules']) == 2 with open('placement_output.json', "wt") as fp: json.dump(placement_data, fp=fp, indent=2) - if DRAW: - draw_placement(placement_data, 'ring_oscillator_0') + draw_placement(placement_data, 'ring_oscillator_0') def test_place_ring_oscillator_stage(): @@ -200,8 +202,20 @@ def test_place_ring_oscillator_stage(): placement_data = pythonic_placer('ring_oscillator_stage', input_data) assert len(placement_data['leaves']) == 2 assert len(placement_data['modules']) == 1 - if DRAW: - draw_placement(placement_data, 'ring_oscillator_stage_0') + draw_placement(placement_data, 'ring_oscillator_stage_0') + + +def test_place_spread(): # Test relies on ALIGN's constraint checker + input_data = ring_oscillator() + modules = {module['name']: module for module in input_data['modules']} + modules['ring_oscillator_stage']['constraints'].append({ + 'constraint': 'Spread', + 'direction': 'vertical', + 'distance': 100, + 'instances': [i['instance_name'] for i in modules['ring_oscillator_stage']['instances']] + }) + placement_data = pythonic_placer('ring_oscillator_stage', input_data, scale_factor=10) + draw_placement(placement_data, 'ring_oscillator_stage_0') def test_propagate_down_global_signals(): diff --git a/tests/placer_pythonic/test_placer_pythonic_sp.py b/tests/placer_pythonic/test_placer_pythonic_sp.py index 3b8fe7364c..f4e8f4bcc6 100644 --- a/tests/placer_pythonic/test_placer_pythonic_sp.py +++ b/tests/placer_pythonic/test_placer_pythonic_sp.py @@ -12,7 +12,9 @@ DRAW = False -def draw(model, instance_map, instance_sizes, wires): +def draw(model, instance_map, instance_sizes=None, wires=None): + if not DRAW: + return colorscale = px.colors.qualitative.Alphabet @@ -34,22 +36,23 @@ def draw(model, instance_map, instance_sizes, wires): y_lst.append((lly+ury)/2) n_lst.append(f"{name}") - i = 0 - for name, instance_bbox in wires.items(): - color = colorscale[i % len(colorscale)] - for instance, bbox in instance_bbox: - size = dict(zip("xy", instance_sizes[instance])) - bb = dict() - for (tag, axis), offset in zip(itertools.product(['ll', 'ur'], ['x', 'y']), bbox): - bb[tag+axis] = model.var_by_name(f'{instance}_ll{axis}').x + offset + \ - (size[axis] - 2*offset) * model.var_by_name(f'{instance}_f{axis}').x - llx, urx = min(bb['llx'], bb['urx']), max(bb['llx'], bb['urx']) - lly, ury = min(bb['lly'], bb['ury']), max(bb['lly'], bb['ury']) - fig.add_shape(type="rect", x0=llx, y0=lly, x1=urx, y1=ury, line=dict(color="Black", width=1), fillcolor=color) - x_lst.append((llx+urx)/2) - y_lst.append((lly+ury)/2) - n_lst.append(f"{name}") - i += 1 + if instance_sizes and wires: + i = 0 + for name, instance_bbox in wires.items(): + color = colorscale[i % len(colorscale)] + for instance, bbox in instance_bbox: + size = dict(zip("xy", instance_sizes[instance])) + bb = dict() + for (tag, axis), offset in zip(itertools.product(['ll', 'ur'], ['x', 'y']), bbox): + bb[tag+axis] = model.var_by_name(f'{instance}_ll{axis}').x + offset + \ + (size[axis] - 2*offset) * model.var_by_name(f'{instance}_f{axis}').x + llx, urx = min(bb['llx'], bb['urx']), max(bb['llx'], bb['urx']) + lly, ury = min(bb['lly'], bb['ury']), max(bb['lly'], bb['ury']) + fig.add_shape(type="rect", x0=llx, y0=lly, x1=urx, y1=ury, line=dict(color="Black", width=1), fillcolor=color) + x_lst.append((llx+urx)/2) + y_lst.append((lly+ury)/2) + n_lst.append(f"{name}") + i += 1 fig.update_shapes(opacity=0.5, xref="x", yref="y") @@ -142,8 +145,7 @@ def test_place_sequence_pair_1(): solution = place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair, wires=wires, place_on_grid=place_on_grid) assert solution['transformations']['M0'] == {'oX': 10, 'oY': 0, 'sX': -1, 'sY': 1} assert solution['transformations']['M1'] == {'oX': 14, 'oY': 4, 'sX': -1, 'sY': -1} - if DRAW: - draw(solution['model'], instance_map, instance_sizes, wires) + draw(solution['model'], instance_map, instance_sizes, wires) @pytest.mark.parametrize("iter", [i for i in range(10)]) @@ -158,8 +160,7 @@ def test_place_sequence_pair_2(iter): ] place_on_grid = {f"M{i}": c for i in range(n)} solution = place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair, place_on_grid=place_on_grid) - if DRAW: - draw(solution['model'], instance_map, [], []) + draw(solution['model'], instance_map) def test_place_sequence_pair_boundary(): @@ -175,8 +176,7 @@ def test_place_sequence_pair_boundary(): constraints.pop() constraints.append(constraint_schema.Boundary(subcircuit="subckt", max_height=10, max_width=10)) solution = place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair) - if DRAW: - draw(solution['model'], instance_map, [], []) + draw(solution['model'], instance_map) for v in solution['model'].vars: if v.name == 'H': break @@ -192,5 +192,4 @@ def test_place_sequence_pair_place_on_boundary(): instance_sizes = {f"M{k}": (1+k, 1+k) for k in range(n)} sequence_pair = ([0, 1, 2, 3], [2, 3, 0, 1]) solution = place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair) - if DRAW: - draw(solution['model'], instance_map, [], []) + draw(solution['model'], instance_map) From c839b6f2872d274de37e9bfd2e7a31c78d0d3eed Mon Sep 17 00:00:00 2001 From: soneryaldiz Date: Wed, 9 Nov 2022 16:17:38 -0800 Subject: [PATCH 10/19] fix and test symmetry --- align/pnr/placer_pythonic_sp.py | 4 +- tests/placer_pythonic/test_placer_pythonic.py | 90 ++++++++++++++++++- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/align/pnr/placer_pythonic_sp.py b/align/pnr/placer_pythonic_sp.py index dad352b12e..7519ba8260 100644 --- a/align/pnr/placer_pythonic_sp.py +++ b/align/pnr/placer_pythonic_sp.py @@ -257,8 +257,8 @@ def place_sequence_pair(constraints, instance_map, instance_sizes, sequence_pair for pair in pairs: if len(pair) == 1: rel_tol = 10 # max distance from symmetry line should be less than 1/10th the block width - model += (1-1/rel_tol)*(model.var_by_name(f'{pair[0]}_ll{axis}'), model.var_by_name(f'{pair[0]}_ur{axis}')) <= 2*symmetry_line - model += (1+1/rel_tol)*(model.var_by_name(f'{pair[0]}_ll{axis}'), model.var_by_name(f'{pair[0]}_ur{axis}')) >= 2*symmetry_line + model += (1-1/rel_tol)*(model.var_by_name(f'{pair[0]}_ll{axis}') + model.var_by_name(f'{pair[0]}_ur{axis}')) <= 2*symmetry_line + model += (1+1/rel_tol)*(model.var_by_name(f'{pair[0]}_ll{axis}') + model.var_by_name(f'{pair[0]}_ur{axis}')) >= 2*symmetry_line else: model += model.var_by_name(f'{pair[0]}_ll{axis}') + model.var_by_name(f'{pair[0]}_ur{axis}') + \ model.var_by_name(f'{pair[1]}_ll{axis}') + model.var_by_name(f'{pair[1]}_ur{axis}') == \ diff --git a/tests/placer_pythonic/test_placer_pythonic.py b/tests/placer_pythonic/test_placer_pythonic.py index 601dc81ead..3301178184 100644 --- a/tests/placer_pythonic/test_placer_pythonic.py +++ b/tests/placer_pythonic/test_placer_pythonic.py @@ -128,7 +128,82 @@ def ring_oscillator(): return data -DRAW = False +def ota(): + place_on_grid = [ + {'constraint': 'PlaceOnGrid', 'direction': 'H', 'pitch': 12600, 'ored_terms': [{'offsets': [0], 'scalings': [1, -1]}]}, + {'constraint': 'PlaceOnGrid', 'direction': 'V', 'pitch': 1080, 'ored_terms': [{'offsets': [0], 'scalings': [1, -1]}]} + ] + data = { + 'leaves': [ + { + 'abstract_template_name': 'NMOS_3T', + 'concrete_template_name': 'NMOS_3T_X2_Y1', + 'bbox': [0, 0, 10800, 6300], + 'terminals': [ + {'layer': 'M2', 'netName': 'D', 'rect': [1000, 1000, 9000, 1500], 'netType': 'pin'}, + {'layer': 'M2', 'netName': 'G', 'rect': [1000, 3000, 9000, 3500], 'netType': 'pin'}, + {'layer': 'M2', 'netName': 'S', 'rect': [1000, 5000, 9000, 5500], 'netType': 'pin'} + ], + 'constraints': place_on_grid + }, + { + 'abstract_template_name': 'PMOS_3T', + 'concrete_template_name': 'PMOS_3T_X2_Y1', + 'bbox': [0, 0, 10800, 6300], + 'terminals': [ + {'layer': 'M2', 'netName': 'D', 'rect': [1000, 1000, 9000, 1500], 'netType': 'pin'}, + {'layer': 'M2', 'netName': 'G', 'rect': [1000, 3000, 9000, 3500], 'netType': 'pin'}, + {'layer': 'M2', 'netName': 'S', 'rect': [1000, 5000, 9000, 5500], 'netType': 'pin'} + ], + 'constraints': place_on_grid + } + ], + 'modules': [ + { + 'name': 'ota', + 'parameters': ['VBIAS', 'VIN', 'VIP', 'VOP', 'VCCX', 'VSSX'], + 'constraints': [], + 'instances': [ + { + 'instance_name': 'XI0', 'abstract_template_name': 'NMOS_3T', + 'fa_map': [{'formal': 'D', 'actual': 'VCM'}, {'formal': 'G', 'actual': 'VBIAS'}, {'formal': 'S', 'actual': 'VSSX'}] + }, + { + 'instance_name': 'XI1', 'abstract_template_name': 'NMOS_3T', + 'fa_map': [{'formal': 'D', 'actual': 'VON'}, {'formal': 'G', 'actual': 'VIP'}, {'formal': 'S', 'actual': 'VCM'}] + }, + { + 'instance_name': 'XI2', 'abstract_template_name': 'NMOS_3T', + 'fa_map': [{'formal': 'D', 'actual': 'VOP'}, {'formal': 'G', 'actual': 'VIN'}, {'formal': 'S', 'actual': 'VCM'}] + }, + { + 'instance_name': 'XI3', 'abstract_template_name': 'PMOS_3T', + 'fa_map': [{'formal': 'D', 'actual': 'VON'}, {'formal': 'G', 'actual': 'VON'}, {'formal': 'S', 'actual': 'VCCX'}] + }, + { + 'instance_name': 'XI4', 'abstract_template_name': 'PMOS_3T', + 'fa_map': [{'formal': 'D', 'actual': 'VOP'}, {'formal': 'G', 'actual': 'VON'}, {'formal': 'S', 'actual': 'VCCX'}] + } + ] + } + ], + 'global_signals': [ + { + "prefix": "global_power", + "formal": "supply0", + "actual": "VSSX" + }, + { + "prefix": "global_power", + "formal": "supply1", + "actual": "VCCX" + } + ] + } + return data + + +DRAW = True def draw_placement(placement_data, module_name): @@ -218,6 +293,19 @@ def test_place_spread(): # Test relies on ALIGN's constraint checker draw_placement(placement_data, 'ring_oscillator_stage_0') +def test_place_floorplan(): # Test relies on ALIGN's constraint checker + input_data = ota() + modules = {module['name']: module for module in input_data['modules']} + modules['ota']['constraints'].append({ + 'constraint': 'Floorplan', + 'order': True, + 'symmetrize': True, + 'regions': [['XI3', 'XI4'], ['XI1', 'XI2'], ['XI0']] + }) + placement_data = pythonic_placer('ota', input_data, scale_factor=10) + draw_placement(placement_data, 'ota_0') + + def test_propagate_down_global_signals(): input_data = ring_oscillator() modules = {module['name']: module for module in input_data['modules']} From 71917330ebfff40c17cd58c3b19f8d41924680c4 Mon Sep 17 00:00:00 2001 From: soneryaldiz Date: Wed, 9 Nov 2022 21:20:06 -0800 Subject: [PATCH 11/19] resolve code review --- align/pnr/placer_pythonic.py | 18 ++++++++---------- tests/placer_pythonic/test_placer_pythonic.py | 5 +++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/align/pnr/placer_pythonic.py b/align/pnr/placer_pythonic.py index a5adefae3f..a79d13bcb9 100644 --- a/align/pnr/placer_pythonic.py +++ b/align/pnr/placer_pythonic.py @@ -30,29 +30,27 @@ def trim_placement_data(placement_data, top_level): modules = {module['concrete_name']: module for module in placement_data['modules']} top_concrete_names = [module['concrete_name'] for module in placement_data['modules'] if module['abstract_name'] == top_level] - all_modules_leaves = list() + all_modules_leaves = set() for concrete_name in top_concrete_names: _, found_modules, found_leaves = compute_topoorder(modules, concrete_name, key='concrete_template_name') - all_modules_leaves.extend(found_modules) - all_modules_leaves.extend(found_leaves) - - all_modules_leaves = set(all_modules_leaves) + all_modules_leaves.update(found_modules) + all_modules_leaves.update(found_leaves) new_placement_data = {'leaves': list(), 'modules': list()} - new_placement_data['leaves'] = [leaf for leaf in placement_data['leaves'] if leaf['concrete_name'] in all_modules_leaves] - new_placement_data['modules'] = [leaf for leaf in placement_data['modules'] if leaf['concrete_name'] in all_modules_leaves] - for key in ['leaves', 'modules']: + new_placement_data[key] = [x for x in placement_data[key] if x['concrete_name'] in all_modules_leaves] for elem in new_placement_data[key]: if 'pin_bbox' in elem: del elem['pin_bbox'] + if 'global_signals' in elem: + elem['global_signals'] = list(elem['global_signals']) return new_placement_data def propagate_down_global_signals(modules: dict, module_name: str, global_signals: list): GS = 'global_signals' - modules[module_name][GS] = modules[module_name].get(GS, list()) + global_signals + modules[module_name][GS] = set.union(modules[module_name].get(GS, set()), global_signals) for instance in modules[module_name]['instances']: sub_module_name = instance['abstract_template_name'] if sub_module_name in modules: @@ -60,7 +58,7 @@ def propagate_down_global_signals(modules: dict, module_name: str, global_signal for formal_actual in instance['fa_map']: formal = formal_actual['formal'] actual = formal_actual['actual'] - if actual in global_signals and formal not in modules[sub_module_name].get(GS, list()): + if actual in global_signals and formal not in modules[sub_module_name].get(GS, set()): signals_to_propagate.append(formal) if signals_to_propagate: propagate_down_global_signals(modules, sub_module_name, signals_to_propagate) diff --git a/tests/placer_pythonic/test_placer_pythonic.py b/tests/placer_pythonic/test_placer_pythonic.py index 3301178184..efd8243c83 100644 --- a/tests/placer_pythonic/test_placer_pythonic.py +++ b/tests/placer_pythonic/test_placer_pythonic.py @@ -2,7 +2,7 @@ import plotly.graph_objects as go import plotly.express as px -from align.pnr.placer_pythonic import pythonic_placer, propagate_down_global_signals +from align.pnr.placer_pythonic import pythonic_placer, propagate_down_global_signals, trim_placement_data from align.cell_fabric.transformation import Transformation, Rect @@ -203,7 +203,7 @@ def ota(): return data -DRAW = True +DRAW = False def draw_placement(placement_data, module_name): @@ -265,6 +265,7 @@ def test_place_ring_oscillator(): with open('placement_input.json', "wt") as fp: fp.write(json.dumps(input_data, indent=2) + '\n') placement_data = pythonic_placer('ring_oscillator', input_data) + placement_data = trim_placement_data(placement_data, 'ring_oscillator') assert len(placement_data['leaves']) == 2 assert len(placement_data['modules']) == 2 with open('placement_output.json', "wt") as fp: From 99b57619775bf5f69cb218f4c4fdc4688fc98596 Mon Sep 17 00:00:00 2001 From: Steven Burns Date: Thu, 10 Nov 2022 09:18:06 -0800 Subject: [PATCH 12/19] Modify .circleci/config.yml to run all tests on each checkin for this branch --- .circleci/config.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index bdf6df2280..32e555760b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -236,6 +236,7 @@ workflows: branches: only: - master + - dev/pythonic_placer matrix: alias: test-integration-cp38-manylinux_2_24 parameters: @@ -255,6 +256,7 @@ workflows: branches: only: - master + - dev/pythonic_placer matrix: alias: build-wheel-all parameters: @@ -282,6 +284,7 @@ workflows: branches: only: - master + - dev/pythonic_placer - publish-wheel: requires: - build-wheel-all @@ -289,4 +292,5 @@ workflows: branches: only: - master + - dev/pythonic_placer From 236b9516d5ac252c076d86b92b04484e9b5efdbf Mon Sep 17 00:00:00 2001 From: Steven Burns Date: Thu, 10 Nov 2022 11:07:52 -0800 Subject: [PATCH 13/19] Maybe 3.10 works --- .circleci/config.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 32e555760b..d37bc35b53 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -192,8 +192,9 @@ workflows: - "cp38-cp38" # Python 3.8 - build-wheel: name: "build-wheel-cp310-manylinux_2_24" - requires: - - build-wheel-cp38-manylinux_2_24 + # This is failing + # requires: + # - build-wheel-cp38-manylinux_2_24 matrix: parameters: platform: From 78c4b42ac814ea328313a1bc34690bd3f066b4fa Mon Sep 17 00:00:00 2001 From: soneryaldiz Date: Thu, 10 Nov 2022 12:17:17 -0800 Subject: [PATCH 14/19] improve efficiency --- align/pnr/placer_pythonic.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/align/pnr/placer_pythonic.py b/align/pnr/placer_pythonic.py index a79d13bcb9..46dd195a93 100644 --- a/align/pnr/placer_pythonic.py +++ b/align/pnr/placer_pythonic.py @@ -48,18 +48,21 @@ def trim_placement_data(placement_data, top_level): return new_placement_data -def propagate_down_global_signals(modules: dict, module_name: str, global_signals: list): +def propagate_down_global_signals(modules: dict, module_name: str, global_signals: set): GS = 'global_signals' - modules[module_name][GS] = set.union(modules[module_name].get(GS, set()), global_signals) + if GS in modules[module_name]: + modules[module_name][GS].update(global_signals) + else: + modules[module_name][GS] = set(global_signals) for instance in modules[module_name]['instances']: sub_module_name = instance['abstract_template_name'] if sub_module_name in modules: - signals_to_propagate = list() + signals_to_propagate = set() for formal_actual in instance['fa_map']: formal = formal_actual['formal'] actual = formal_actual['actual'] if actual in global_signals and formal not in modules[sub_module_name].get(GS, set()): - signals_to_propagate.append(formal) + signals_to_propagate.add(formal) if signals_to_propagate: propagate_down_global_signals(modules, sub_module_name, signals_to_propagate) @@ -96,7 +99,7 @@ def pythonic_placer(top_level, input_data, scale_factor=1): topological_order, found_modules, _ = compute_topoorder(modules, top_level) # Propagate power pins down the modules - if global_signals := [x['actual'] for x in input_data['global_signals']]: + if global_signals := {x['actual'] for x in input_data['global_signals']}: propagate_down_global_signals(modules, top_level, global_signals) for name in topological_order: From c4e14c7b52b3083eb9ef77f7abe47f0f073a17f5 Mon Sep 17 00:00:00 2001 From: Steven Burns Date: Thu, 10 Nov 2022 12:46:17 -0800 Subject: [PATCH 15/19] Hacking circleci config.yaml: --- .circleci/config.yml | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d37bc35b53..51bb83e065 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,7 +17,7 @@ jobs: # https://github.com/pypa/manylinux description: "PEP600 compatible platform name" type: string - default: "quay.io/pypa/manylinux_2_24_x86_64" + default: "quay.io/pypa/manylinux_2_28_x86_64" docker: - image: <> @@ -96,7 +96,7 @@ jobs: ALIGN_HOME: /root/ALIGN-public ALIGN_WORK_DIR: /root/ALIGN-public/work MAX_JOBS: 2 - BUILD_PLATFORM: manylinux_2_24_x86_64 + BUILD_PLATFORM: manylinux_2_28_x86_64 steps: - run: @@ -183,28 +183,27 @@ workflows: jobs: - build-test-cktgen - build-wheel: - name: "build-wheel-cp38-manylinux_2_24" + name: "build-wheel-cp38-manylinux_2_28" matrix: parameters: platform: - - "quay.io/pypa/manylinux_2_24_x86_64" # PEP 600 + - "quay.io/pypa/manylinux_2_28_x86_64" # PEP 600 python-tags: - "cp38-cp38" # Python 3.8 - build-wheel: - name: "build-wheel-cp310-manylinux_2_24" - # This is failing - # requires: - # - build-wheel-cp38-manylinux_2_24 + name: "build-wheel-cp310-manylinux_2_28" + requires: + - build-wheel-cp38-manylinux_2_28 matrix: parameters: platform: - - "quay.io/pypa/manylinux_2_24_x86_64" # PEP 600 + - "quay.io/pypa/manylinux_2_28_x86_64" # PEP 600 python-tags: - "cp310-cp310" # Python 3.10 # - build-wheel: # name: "build-wheel-cp37-manylinux2010" # requires: - # - build-wheel-cp38-manylinux_2_24 + # - build-wheel-cp38-manylinux_2_28 # matrix: # parameters: # platform: @@ -214,7 +213,7 @@ workflows: - test-integration: name: "test-integration-cp38-minimal" requires: - - build-wheel-cp38-manylinux_2_24 + - build-wheel-cp38-manylinux_2_28 filters: branches: ignore: @@ -232,14 +231,13 @@ workflows: - test-integration: name: "test-integration-cp38-<>" requires: - - build-wheel-cp38-manylinux_2_24 + - build-wheel-cp38-manylinux_2_28 filters: branches: only: - master - - dev/pythonic_placer matrix: - alias: test-integration-cp38-manylinux_2_24 + alias: test-integration-cp38-manylinux_2_28 parameters: platform: - "python:3.8" @@ -252,19 +250,18 @@ workflows: - build-wheel: name: "build-wheel-<>-<>" requires: - - test-integration-cp38-manylinux_2_24 + - test-integration-cp38-manylinux_2_28 filters: branches: only: - master - - dev/pythonic_placer matrix: alias: build-wheel-all parameters: platform: # - "quay.io/pypa/manylinux2010_x86_64" # PEP 571 - "quay.io/pypa/manylinux2014_x86_64" # PEP 599 - - "quay.io/pypa/manylinux_2_24_x86_64" # PEP 600 + - "quay.io/pypa/manylinux_2_28_x86_64" # PEP 600 python-tags: # - "cp37-cp37m" - "cp38-cp38" @@ -274,18 +271,17 @@ workflows: # other jobs - platform: "quay.io/pypa/manylinux2010_x86_64" python-tags: "cp37-cp37m" - - platform: "quay.io/pypa/manylinux_2_24_x86_64" + - platform: "quay.io/pypa/manylinux_2_28_x86_64" python-tags: "cp38-cp38" - - platform: "quay.io/pypa/manylinux_2_24_x86_64" + - platform: "quay.io/pypa/manylinux_2_28_x86_64" python-tags: "cp310-cp310" - build-test-coverage: requires: - - build-wheel-cp38-manylinux_2_24 + - build-wheel-cp38-manylinux_2_28 filters: branches: only: - master - - dev/pythonic_placer - publish-wheel: requires: - build-wheel-all @@ -293,5 +289,4 @@ workflows: branches: only: - master - - dev/pythonic_placer From 672e682dacc55e7c41c00b5d3b99c7312fec9cce Mon Sep 17 00:00:00 2001 From: Steven Burns Date: Thu, 10 Nov 2022 12:48:21 -0800 Subject: [PATCH 16/19] Added 2_28 wheels --- bin/build_wheel.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/build_wheel.sh b/bin/build_wheel.sh index 6c62edc2f3..9b15dbf419 100755 --- a/bin/build_wheel.sh +++ b/bin/build_wheel.sh @@ -25,6 +25,10 @@ case "$AUDITWHEEL_PLAT" in apt update apt -y install libboost-dev lp-solve ;; + "manylinux_2_28_x86_64") + apt update + apt -y install libboost-dev lp-solve + ;; *) echo "WARNING: Unknown environment." echo "Please make sure you are using a supported manylinux platform to run this script" From 1eaab42c6f7572f2f60d970a14ebe7da8ffc9cf3 Mon Sep 17 00:00:00 2001 From: Steven Burns Date: Thu, 10 Nov 2022 12:50:37 -0800 Subject: [PATCH 17/19] Update build_wheel.sh --- bin/build_wheel.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/build_wheel.sh b/bin/build_wheel.sh index 9b15dbf419..60ed308408 100755 --- a/bin/build_wheel.sh +++ b/bin/build_wheel.sh @@ -26,8 +26,9 @@ case "$AUDITWHEEL_PLAT" in apt -y install libboost-dev lp-solve ;; "manylinux_2_28_x86_64") - apt update - apt -y install libboost-dev lp-solve + # Not debian based (AlmaLinux 8) + #apt update + #apt -y install libboost-dev lp-solve ;; *) echo "WARNING: Unknown environment." From 558433668370a84c07fdea0bfedff4ed5f505d06 Mon Sep 17 00:00:00 2001 From: Steven Burns Date: Fri, 11 Nov 2022 02:49:59 -0800 Subject: [PATCH 18/19] Make sure master CI works for this branch as well --- .circleci/config.yml | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 51bb83e065..40667abe99 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -190,16 +190,16 @@ workflows: - "quay.io/pypa/manylinux_2_28_x86_64" # PEP 600 python-tags: - "cp38-cp38" # Python 3.8 - - build-wheel: - name: "build-wheel-cp310-manylinux_2_28" - requires: - - build-wheel-cp38-manylinux_2_28 - matrix: - parameters: - platform: - - "quay.io/pypa/manylinux_2_28_x86_64" # PEP 600 - python-tags: - - "cp310-cp310" # Python 3.10 + # - build-wheel: + # name: "build-wheel-cp310-manylinux_2_28" + # requires: + # - build-wheel-cp38-manylinux_2_28 + # matrix: + # parameters: + # platform: + # - "quay.io/pypa/manylinux_2_28_x86_64" # PEP 600 + # python-tags: + # - "cp310-cp310" # Python 3.10 # - build-wheel: # name: "build-wheel-cp37-manylinux2010" # requires: @@ -236,6 +236,7 @@ workflows: branches: only: - master + - dev/pythonic_placer matrix: alias: test-integration-cp38-manylinux_2_28 parameters: @@ -255,6 +256,7 @@ workflows: branches: only: - master + - dev/pythonic_placer matrix: alias: build-wheel-all parameters: @@ -269,12 +271,12 @@ workflows: exclude: # Skipping these as they have been built by # other jobs - - platform: "quay.io/pypa/manylinux2010_x86_64" - python-tags: "cp37-cp37m" + # - platform: "quay.io/pypa/manylinux2010_x86_64" + # python-tags: "cp37-cp37m" - platform: "quay.io/pypa/manylinux_2_28_x86_64" python-tags: "cp38-cp38" - - platform: "quay.io/pypa/manylinux_2_28_x86_64" - python-tags: "cp310-cp310" + # - platform: "quay.io/pypa/manylinux_2_28_x86_64" + # python-tags: "cp310-cp310" - build-test-coverage: requires: - build-wheel-cp38-manylinux_2_28 @@ -282,6 +284,7 @@ workflows: branches: only: - master + - dev/pythonic_placer - publish-wheel: requires: - build-wheel-all @@ -289,4 +292,4 @@ workflows: branches: only: - master - + - dev/pythonic_placer From 2e11952f9aea9f99f9efea26308ff2bf8cbd0caa Mon Sep 17 00:00:00 2001 From: Steven Burns Date: Fri, 11 Nov 2022 16:38:39 -0800 Subject: [PATCH 19/19] Change build to use shared libraries (maybe). --- .circleci/config.yml | 3 ++- PlaceRouteHierFlow/thirdparty/ilpif.cmake | 24 +++++++++++------------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 40667abe99..ce8f925a59 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -262,11 +262,12 @@ workflows: parameters: platform: # - "quay.io/pypa/manylinux2010_x86_64" # PEP 571 - - "quay.io/pypa/manylinux2014_x86_64" # PEP 599 + # - "quay.io/pypa/manylinux2014_x86_64" # PEP 599 - "quay.io/pypa/manylinux_2_28_x86_64" # PEP 600 python-tags: # - "cp37-cp37m" - "cp38-cp38" + - "cp311-cp311" - "cp310-cp310" exclude: # Skipping these as they have been built by diff --git a/PlaceRouteHierFlow/thirdparty/ilpif.cmake b/PlaceRouteHierFlow/thirdparty/ilpif.cmake index 54afab2db0..759727cb4e 100644 --- a/PlaceRouteHierFlow/thirdparty/ilpif.cmake +++ b/PlaceRouteHierFlow/thirdparty/ilpif.cmake @@ -26,18 +26,18 @@ if(NOT ilpsolverif_POPULATED) add_library(ilp_solver::ilp_solver ALIAS ILPSolverIf) else() message(STATUS "ILP solver lib found in ${ilp_solver_lib}") - add_library(ilp_solver_if STATIC IMPORTED) - add_library(cbc_if STATIC IMPORTED) - add_library(clp_if STATIC IMPORTED) - add_library(cgl_if STATIC IMPORTED) - add_library(osi_if STATIC IMPORTED) - add_library(osiclp_if STATIC IMPORTED) - add_library(osicbc_if STATIC IMPORTED) - add_library(coinutils_if STATIC IMPORTED) - add_library(clpsolver_if STATIC IMPORTED) - add_library(cbcsolver_if STATIC IMPORTED) - add_library(sym_if STATIC IMPORTED) - add_library(osisym_if STATIC IMPORTED) + add_library(ilp_solver_if SHARED IMPORTED) + add_library(cbc_if SHARED IMPORTED) + add_library(clp_if SHARED IMPORTED) + add_library(cgl_if SHARED IMPORTED) + add_library(osi_if SHARED IMPORTED) + add_library(osiclp_if SHARED IMPORTED) + add_library(osicbc_if SHARED IMPORTED) + add_library(coinutils_if SHARED IMPORTED) + add_library(clpsolver_if SHARED IMPORTED) + add_library(cbcsolver_if SHARED IMPORTED) + add_library(sym_if SHARED IMPORTED) + add_library(osisym_if SHARED IMPORTED) set_property(TARGET ilp_solver_if PROPERTY IMPORTED_LOCATION ${ilp_solver_lib}) target_include_directories(ilp_solver_if INTERFACE ${ilpsolverif_SOURCE_DIR}/ILPSolverIf) target_include_directories(ilp_solver_if INTERFACE ${solver_search_path})