diff --git a/CodeElements/Plc_client/placement_util_os.py b/CodeElements/Plc_client/placement_util_os.py new file mode 100644 index 0000000..c0d799f --- /dev/null +++ b/CodeElements/Plc_client/placement_util_os.py @@ -0,0 +1,970 @@ +# coding=utf-8 +# Copyright 2021 The Circuit Training Team Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""A collection of non-prod utility functions for placement. + +All the dependencies in this files should be non-prod. +""" + +import os +import datetime +import re +import textwrap +from typing import Dict, Iterator, List, Optional, Tuple, Any, Union + +from absl import logging +logging.set_verbosity(logging.INFO) +# from circuit_training.environment import plc_client +from Plc_client import plc_client_os as plc_client +import numpy as np + +# Internal gfile dependencies + + +def nodes_of_types(plc: plc_client.PlacementCost, + type_list: List[str]) -> Iterator[int]: + """Yields the index of a node of certain types.""" + i = 0 + while True: + node_type = plc.get_node_type(i) + if not node_type: + break + if node_type in type_list: + yield i + i += 1 + + +def get_node_xy_coordinates( + plc: plc_client.PlacementCost) -> Dict[int, Tuple[float, float]]: + """Returns all node x,y coordinates (canvas) in a dict.""" + node_coords = dict() + for node_index in nodes_of_types(plc, ['MACRO', 'STDCELL', 'PORT']): + if plc.is_node_placed(node_index): + node_coords[node_index] = plc.get_node_location(node_index) + return node_coords + + +def get_macro_orientations(plc: plc_client.PlacementCost) -> Dict[int, int]: + """Returns all macros' orientations in a dict.""" + macro_orientations = dict() + for node_index in nodes_of_types(plc, ['MACRO']): + macro_orientations[node_index] = plc.get_macro_orientation(node_index) + return macro_orientations + + +def restore_node_xy_coordinates( + plc: plc_client.PlacementCost, + node_coords: Dict[int, Tuple[float, float]]) -> None: + for node_index, coords in node_coords.items(): + if not plc.is_node_fixed(node_index): + plc.update_node_coords(node_index, coords[0], coords[1]) + + +def restore_macro_orientations(plc: plc_client.PlacementCost, + macro_orientations: Dict[int, int]) -> None: + for node_index, orientation in macro_orientations.items(): + plc.update_macro_orientation(node_index, orientation) + + +def extract_attribute_from_comments(attribute: str, + filenames: List[str]) -> Optional[str]: + """Parses the files' comments section, tries to extract the attribute. + + Args: + attribute: attribute to look for (case sensetive). + filenames: List of protobuf file or a plc file. + + Returns: + Attribute name string, or None if not found. + """ + for filename in filenames: + if filename: + f = filename.split(',')[0] + if f: + with open(f, 'rt') as infile: + for line in infile: + if line.startswith('#'): + match = re.search(fr'{attribute} : ([-\w]+)', line) + if match: + return match.group(1) + else: + # Do not parse the rest of the file, since all the comments are at + # the top. + break + return None + + +def get_blockages_from_comments( + filenames: Union[str, List[str]]) -> Optional[List[List[float]]]: + """Returns list of blockages if they exist in the file's comments section.""" + for filename in filenames: + if not filename: + continue + blockages = [] + # Read the first file if filename is comma separated list. + # Expected blockage info line format is: + # "# Blockage : <float> <float> <float> <float> <float>" + # where first four float numbers correspond to minx, miny, maxx, maxy of + # the rectangular region, the fifth one is the blockage rate. It's usually + # set to 1. + try: + with open(filename, 'rt') as infile: + for line in infile: + if line.startswith('# Blockage : '): + blockages.append([float(x) for x in line.split()[3:8]]) + elif not line.startswith('#'): + break + except OSError: + logging.error('could not read file %s.', filename) + if blockages: + return blockages + + +def extract_sizes_from_comments( + filenames: List[str]) -> Optional[Tuple[float, float, int, int]]: + """Parses the file's comments section, tries to extract canvas/grid sizes. + + Args: + filenames: A list of netlist (.pb.txt) or placement (.plc) files. + + Returns: + Tuple of canvas_width, canvas_height, grid_cols, grid_rows + """ + for filename in filenames: + if not filename: + continue + canvas_width, canvas_height = None, None + grid_cols, grid_rows = None, None + with open(filename, 'rt') as infile: + for line in infile: + if line.startswith('#'): + fp_re = re.search( + r'FP bbox: \{([\d\.]+) ([\d\.]+)\} \{([\d\.]+) ([\d\.]+)\}', line) + if fp_re: + canvas_width = float(fp_re.group(3)) + canvas_height = float(fp_re.group(4)) + continue + plc_wh = re.search(r'Width : ([\d\.]+) Height : ([\d\.]+)', line) + if plc_wh: + canvas_width = float(plc_wh.group(1)) + canvas_height = float(plc_wh.group(2)) + continue + plc_cr = re.search(r'Columns : ([\d]+) Rows : ([\d]+)', line) + if plc_cr: + grid_cols = int(plc_cr.group(1)) + grid_rows = int(plc_cr.group(2)) + else: + # Do not parse the rest of the file, since all the comments are at the + # top. + break + if canvas_width and canvas_height and grid_cols and grid_rows: + return canvas_width, canvas_height, grid_cols, grid_rows + + +def fix_port_coordinates(plc: plc_client.PlacementCost) -> None: + """Find all ports and fix their coordinates. + + Args: + plc: the placement cost object. + """ + for node in nodes_of_types(plc, ['PORT']): + # print("node to fix:", node) + plc.fix_node_coord(node) + + +# The routing capacities are calculated based on the public information about +# 7nm technology (https://en.wikichip.org/wiki/7_nm_lithography_process) +# with an arbitary, yet reasonable, assumption of 18% of the tracks for +# the power grids. +def create_placement_cost( + netlist_file: str, + init_placement: Optional[str] = None, + overlap_threshold: float = 4e-3, + congestion_smooth_range: int = 2, + # TODO(b/211039937): Increase macro spacing to 3-5um, after matching the + # performance for Ariane. + macro_macro_x_spacing: float = 0.1, + macro_macro_y_spacing: float = 0.1, + boundary_check: bool = False, + horizontal_routes_per_micron: float = 70.33, + vertical_routes_per_micron: float = 74.51, + macro_horizontal_routing_allocation: float = 51.79, + macro_vertical_routing_allocation: float = 51.79, +) -> plc_client.PlacementCost: + """Creates a placement_cost object. + + Args: + netlist_file: Path to the netlist proto text file. + init_placement: Path to the inital placement .plc file. + overlap_threshold: Used for macro overlap detection. + congestion_smooth_range: Smoothing factor used for congestion estimation. + Congestion is distributed to this many neighboring columns/rows.' + macro_macro_x_spacing: Macro-to-macro x spacing in microns. + macro_macro_y_spacing: Macro-to-macro y spacing in microns. + boundary_check: Do a boundary check during node placement. + horizontal_routes_per_micron: Horizontal route capacity per micros. + vertical_routes_per_micron: Vertical route capacity per micros. + macro_horizontal_routing_allocation: Macro horizontal routing allocation. + macro_vertical_routing_allocation: Macro vertical routing allocation. + + Returns: + A PlacementCost object. + """ + if not netlist_file: + raise ValueError('netlist_file should be provided.') + + block_name = extract_attribute_from_comments('Block', + [init_placement, netlist_file]) + if not block_name: + logging.warning( + 'block_name is not set. ' + 'Please add the block_name in:\n%s\nor in:\n%s', netlist_file, + init_placement) + + plc = plc_client.PlacementCost(netlist_file, macro_macro_x_spacing, + macro_macro_y_spacing) + + blockages = get_blockages_from_comments([netlist_file, init_placement]) + if blockages: + print(blockages) + for blockage in blockages: + print(*blockage) + plc.create_blockage(*blockage) + + sizes = extract_sizes_from_comments([netlist_file, init_placement]) + print(sizes) + if sizes: + canvas_width, canvas_height, grid_cols, grid_rows = sizes + if canvas_width and canvas_height and grid_cols and grid_rows: + plc.set_canvas_size(canvas_width, canvas_height) + plc.set_placement_grid(grid_cols, grid_rows) + + plc.set_project_name('circuit_training') + plc.set_block_name(block_name or 'unset_block') + plc.set_routes_per_micron(horizontal_routes_per_micron, + vertical_routes_per_micron) + plc.set_macro_routing_allocation(macro_horizontal_routing_allocation, + macro_vertical_routing_allocation) + plc.set_congestion_smooth_range(congestion_smooth_range) + plc.set_overlap_threshold(overlap_threshold) + plc.set_canvas_boundary_check(boundary_check) + plc.make_soft_macros_square() + # exit(0) + # print(plc.get_soft_macros_count()) + if init_placement: + plc.restore_placement(init_placement) + fix_port_coordinates(plc) + + return plc + + +def get_node_type_counts(plc: plc_client.PlacementCost) -> Dict[str, int]: + """Returns number of each type of nodes in the netlist. + + Args: + plc: the placement cost object. + + Returns: + Number of each type of node in a dict. + """ + counts = { + 'MACRO': 0, + 'STDCELL': 0, + 'PORT': 0, + 'MACRO_PIN': 0, + 'SOFT_MACRO': 0, + 'HARD_MACRO': 0, + 'SOFT_MACRO_PIN': 0, + 'HARD_MACRO_PIN': 0 + } + + for node_index in nodes_of_types(plc, + ['MACRO', 'STDCELL', 'PORT', 'MACRO_PIN']): + node_type = plc.get_node_type(node_index) + counts[node_type] += 1 + if node_type == 'MACRO': + if plc.is_node_soft_macro(node_index): + counts['SOFT_MACRO'] += 1 + else: + counts['HARD_MACRO'] += 1 + if node_type == 'MACRO_PIN': + ref_id = plc.get_ref_node_id(node_index) + if plc.is_node_soft_macro(ref_id): + counts['SOFT_MACRO_PIN'] += 1 + else: + counts['HARD_MACRO_PIN'] += 1 + return counts + + +def make_blockage_text(plc: plc_client.PlacementCost) -> str: + ret = '' + for blockage in plc.get_blockages(): + ret += 'Blockage : {}\n'.format(' '.join([str(b) for b in blockage])) + return ret + + +def save_placement(plc: plc_client.PlacementCost, + filename: str, + user_comments: str = '') -> None: + """Saves the placement file with some information in the comments section.""" + cols, rows = plc.get_grid_num_columns_rows() + width, height = plc.get_canvas_width_height() + hor_routes, ver_routes = plc.get_routes_per_micron() + hor_macro_alloc, ver_macro_alloc = plc.get_macro_routing_allocation() + smooth = plc.get_congestion_smooth_range() + info = textwrap.dedent("""\ + Placement file for Circuit Training + Source input file(s) : {src_filename} + This file : {filename} + Date : {date} + Columns : {cols} Rows : {rows} + Width : {width:.3f} Height : {height:.3f} + Area : {area} + Wirelength : {wl:.3f} + Wirelength cost : {wlc:.4f} + Congestion cost : {cong:.4f} + Density cost : {density:.4f} + Project : {project} + Block : {block_name} + Routes per micron, hor : {hor_routes:.3f} ver : {ver_routes:.3f} + Routes used by macros, hor : {hor_macro_alloc:.3f} ver : {ver_macro_alloc:.3f} + Smoothing factor : {smooth} + Overlap threshold : {overlap_threshold} + """.format( + src_filename=plc.get_source_filename(), + filename=filename, + date=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + cols=cols, + rows=rows, + width=width, + height=height, + area=plc.get_area(), + wl=plc.get_wirelength(), + wlc=plc.get_cost(), + cong=plc.get_congestion_cost(), + density=plc.get_density_cost(), + project=plc.get_project_name(), + block_name=plc.get_block_name(), + hor_routes=hor_routes, + ver_routes=ver_routes, + hor_macro_alloc=hor_macro_alloc, + ver_macro_alloc=ver_macro_alloc, + smooth=smooth, + overlap_threshold=plc.get_overlap_threshold())) + + info += '\n' + make_blockage_text(plc) + '\n' + info += '\nCounts of node types:\n' + node_type_counts = get_node_type_counts(plc) + for node_type in sorted(node_type_counts): + info += '{:<15} : {:>9}\n'.format(node_type + 's', + node_type_counts[node_type]) + if user_comments: + info += '\nUser comments:\n' + user_comments + '\n' + info += '\nnode_index x y orientation fixed' + return plc.save_placement(filename, info) + + +def fd_placement_schedule(plc: plc_client.PlacementCost, + num_steps: Tuple[int, ...] = (100, 100, 100), + io_factor: float = 1.0, + move_distance_factors: Tuple[float, + ...] = (1.0, 1.0, 1.0), + attract_factor: Tuple[float, + ...] = (100.0, 1.0e-3, 1.0e-5), + repel_factor: Tuple[float, ...] = (0.0, 1.0e6, 1.0e7), + use_current_loc: bool = False, + move_macros: bool = False) -> None: + """A placement schedule that uses force directed method. + + Args: + plc: The plc object. + num_steps: Number of steps of the force-directed algorithm during each call. + io_factor: I/O attract factor. + move_distance_factors: Maximum distance relative to canvas size that a node + can move in a single step of the force-directed algorithm. + attract_factor: The spring constants between two connected nodes in the + force-directed algorithm. The FD algorithm will be called size of this + list times. Make sure that the size of fd_repel_factor has the same size. + repel_factor: The repellent factor for spreading the nodes to avoid + congestion in the force-directed algorithm.' + use_current_loc: If true, use the current location as the initial location. + move_macros: If true, also move the macros. + """ + assert len(num_steps) == len(move_distance_factors) + assert len(num_steps) == len(repel_factor) + assert len(num_steps) == len(attract_factor) + canvas_size = max(plc.get_canvas_width_height()) + max_move_distance = [ + f * canvas_size / s for s, f in zip(num_steps, move_distance_factors) + ] + move_stdcells = True + log_scale_conns = False + use_sizes = False + plc.optimize_stdcells(use_current_loc, move_stdcells, move_macros, + log_scale_conns, use_sizes, io_factor, num_steps, + max_move_distance, attract_factor, repel_factor) + + +def get_ordered_node_indices(mode: str, + plc: plc_client.PlacementCost, + exclude_fixed_nodes: bool = True) -> List[int]: + """Returns an ordering of node indices according to the specified mode. + + Args: + mode: node ordering mode + plc: placement cost object + exclude_fixed_nodes: Whether fixed nodes should be excluded. + + Returns: + Node indices sorted according to the mode. + """ + macro_indices = plc.get_macro_indices() + hard_macro_indices = [ + m for m in macro_indices if not plc.is_node_soft_macro(m) + ] + soft_macro_indices = [m for m in macro_indices if plc.is_node_soft_macro(m)] + + def macro_area(idx): + w, h = plc.get_node_width_height(idx) + return w * h + + if mode == 'descending_size_macro_first': + ordered_indices = ( + sorted(hard_macro_indices, key=macro_area)[::-1] + + sorted(soft_macro_indices, key=macro_area)[::-1]) + elif mode == 'random': + np.random.shuffle(macro_indices) + ordered_indices = macro_indices + elif mode == 'random_macro_first': + np.random.shuffle(hard_macro_indices) + ordered_indices = hard_macro_indices + soft_macro_indices + else: + raise ValueError('{} is an unsupported node placement mode.'.format(mode)) + + if exclude_fixed_nodes: + ordered_indices = [m for m in ordered_indices if not plc.is_node_fixed(m)] + return ordered_indices + + +def extract_parameters_from_comments( + filename: str) -> Tuple[float, float, int, int]: + """Parses the file's comments section, tries to extract canvas/grid sizes. + + Args: + filename: protobuf file or a plc file. + + Returns: + Tuple of canvas_width, canvas_height, grid_cols, grid_rows + """ + filename0 = filename.split(',')[0] + canvas_width, canvas_height = None, None + grid_cols, grid_rows = None, None + with open(filename0, 'r') as infile: + for line in infile: + if line.startswith('#'): + fp_re = re.search( + r'FP bbox: \{([\d\.]+) ([\d\.]+)\} \{([\d\.]+) ([\d\.]+)\}', line) + if fp_re: + canvas_width = float(fp_re.group(3)) + canvas_height = float(fp_re.group(4)) + continue + plc_wh = re.search(r'Width : ([\d\.]+) Height : ([\d\.]+)', line) + if plc_wh: + canvas_width = float(plc_wh.group(1)) + canvas_height = float(plc_wh.group(2)) + continue + plc_cr = re.search(r'Columns : ([\d]+) Rows : ([\d]+)', line) + if plc_cr: + grid_cols = int(plc_cr.group(1)) + grid_rows = int(plc_cr.group(2)) + else: + # Do not parse the rest of the file, since all the comments are at the + # top. + break + return canvas_width, canvas_height, grid_cols, grid_rows + + +def get_routing_resources() -> Dict[str, float]: + """Currently we only use default parameter settings. + + In the future, for specific project, the resources may need to be tuned. + + Returns: + Routing resources. + """ + + return { + 'horizontal_routes_per_micron': 57.031, + 'vertical_routes_per_micron': 56.818, + 'macro_horizontal_routing_allocation': 39.583, + 'macro_vertical_routing_allocation': 30.303, + } + + +def nodes_of_types(plc: plc_client.PlacementCost, type_list: List[str]): + """Yields the index of a node of certain types.""" + i = 0 + while True: + node_type = plc.get_node_type(i) + if not node_type: + break + if node_type in type_list: + yield i + i += 1 + + +def num_nodes_of_type(plc, node_type): + """Returns number of node of a particular type.""" + count = 0 + for _ in nodes_of_types(plc, [node_type]): + count += 1 + return count + + +def extract_blockages_from_tcl(filename: str, + block_name: str, + canvas_width: float, + canvas_height: float, + is_rectilinear: bool = False): + """Reads blockage information from a given tcl file.""" + # Assumptions: project is viperlite or viperfish. + # This is not a TCL parser, it just reads in a line of the format: + # dict set ::clockstrap <block name> <blockage index> <corner> <float number> + # corner is expected to be one of lly, ury. + blockage_info = dict() + try: + with open(filename, 'r') as infile: + for line in infile: + if line.startswith('dict set ::clockstrap '): + block, index, corner, value = line.split()[3:7] + if block != block_name: + continue + blockage_info[corner + index] = float(value) + except gfile.FileError: + logging.error('could not read file %s', filename) + return [] + blockages = [] + + if is_rectilinear: + # Use blockage to model rectilinear floorplan. + index = 0 + while ('llx' + str(index) in blockage_info and + 'lly' + str(index) in blockage_info and + 'urx' + str(index) in blockage_info and + 'ury' + str(index) in blockage_info): + minx = blockage_info['llx' + str(index)] + maxx = blockage_info['urx' + str(index)] + miny = blockage_info['lly' + str(index)] + maxy = blockage_info['ury' + str(index)] + if minx < 0: + raise ValueError(f'Illegal blockage at index {index}: llx {minx} < 0') + if maxx > canvas_width: + raise ValueError( + f'Illegal blockage at index {index}: urx {maxx} > canvas ' + f'width {canvas_width}') + if miny < 0: + raise ValueError(f'Illegal blockage at index {index}: lly {miny} < 0') + if maxy > canvas_height: + raise ValueError( + f'Illegal blockage at index {index}: ury {maxy} > canvas ' + f'height {canvas_height}') + blockages.append([minx, miny, maxx, maxy, 1]) + index += 1 + else: + # Fully horizontal or vertical blockage. + # Horizontal straps. + index = 0 + while 'lly' + str(index) in blockage_info and 'ury' + str( + index) in blockage_info: + minx = 0.0 + maxx = canvas_width + miny = blockage_info['lly' + str(index)] + maxy = blockage_info['ury' + str(index)] + blockages.append([minx, miny, maxx, maxy, 1]) + index += 1 + # We don't have any vertical straps, now. Should we still support it? + # Vertical straps. + index = 0 + while 'llx' + str(index) in blockage_info and 'urx' + str( + index) in blockage_info: + minx = blockage_info['llx' + str(index)] + maxx = blockage_info['urx' + str(index)] + miny = 0.0 + maxy = canvas_height + blockages.append([minx, miny, maxx, maxy, 1]) + index += 1 + return blockages + + +def get_ascii_picture(vect: List[Any], + cols: int, + rows: int, + scale: float = 10) -> str: + """Returns an ascii picture for the input as a human readable matrix.""" + ret_str = ' ' + for c in range(cols): + ret_str += '|' + str(int(c / 10) % 10) + ret_str += '|\n ' + for c in range(cols): + ret_str += '|' + str(c % 10) + ret_str += '|\n -' + '-' * 2 * cols + '\n' + for r in range(rows - 1, -1, -1): + ret_str += format('%3d' % r) + for c in range(cols): + mindex = r * cols + c + val = int(scale * vect[mindex]) + if val > scale: + ret_str += '|!' + elif val == scale: + ret_str += '|#' + elif val == 0: + ret_str += '| ' + else: + ret_str += '|' + str(val) + ret_str += '|\n' + ret_str += ' -' + '-' * 2 * cols + '\n' + return ret_str + + +def get_hard_macro_density_map(plc: plc_client.PlacementCost) -> List[float]: + """Returns the placement density map for hard macros only.""" + # Unplaces all standard cells and soft macros, so that grid cell density + # only contains hard macros. + placements_to_restore = dict() + for node_index in nodes_of_types(plc, ['STDCELL']): + if plc.is_node_placed(node_index): + placements_to_restore[node_index] = plc.get_node_location(node_index) + plc.unplace_node(node_index) + for node_index in nodes_of_types(plc, ['MACRO']): + if plc.is_node_soft_macro(node_index) and plc.is_node_placed(node_index): + placements_to_restore[node_index] = plc.get_node_location(node_index) + plc.unplace_node(node_index) + hard_macro_density = plc.get_grid_cells_density() + check_boundary = plc.get_canvas_boundary_check() + # Restores placements, but original placement may be illegal (outside canvas + # area), ignore those cases. + plc.set_canvas_boundary_check(False) + for node_index, coords in placements_to_restore.items(): + plc.update_node_coords(node_index, coords[0], coords[1]) + plc.set_canvas_boundary_check(check_boundary) + return hard_macro_density + + +def save_placement_with_info(plc: plc_client.PlacementCost, + filename: str, + user_comments: str = '') -> None: + """Saves the placement file with some information in the comments section.""" + cols, rows = plc.get_grid_num_columns_rows() + width, height = plc.get_canvas_width_height() + hor_routes, ver_routes = plc.get_routes_per_micron() + hor_macro_alloc, ver_macro_alloc = plc.get_macro_routing_allocation() + smooth = plc.get_congestion_smooth_range() + init_placement_config = '' + # Do not change the format of the comments section before updating + # extract_parameters_from_comments and extract_netlist_file_from_comments + # functions. + info = textwrap.dedent("""\ + Placement file for Circuit Training + Source input file(s) : {src_filename} + This file : {filename} + Original initial placement : {init_placement_config} + Date : {date} + Columns : {cols} Rows : {rows} + Width : {width:.3f} Height : {height:.3f} + Area (stdcell+macros) : {area} + Wirelength : {wl:.3f} + Wirelength cost : {wlc:.4f} + Congestion cost : {cong:.4f} + Density cost : {density:.4f} + Fake net cost : {fake_net:.4f} + 90% Congestion metric: {cong90} + Project : {project} + Block : {block_name} + Routes per micron, hor : {hor_routes:.3f} ver : {ver_routes:.3f} + Routes used by macros, hor : {hor_macro_alloc:.3f} ver : {ver_macro_alloc:.3f} + Smoothing factor : {smooth} + Use incremental cost : {incr_cost} + + To view this file (most options are default): + viewer_binary\ + --netlist_file {src_filename}\ + --canvas_width {width} --canvas_height {height}\ + --grid_cols {cols} --grid_rows={rows}\ + --init_placement {filename}\ + --project {project}\ + --block_name {block_name}\ + --congestion_smooth_range {smooth}\ + --overlap_threshold {overlap_threshold}\ + --noboundary_check + or you can simply run: + viewer_binary\ + --init_placement {filename} + """.format( + src_filename=plc.get_source_filename(), + filename=filename, + init_placement_config=init_placement_config, + date=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + cols=cols, + rows=rows, + width=width, + height=height, + area=plc.get_area(), + wl=plc.get_wirelength(), + wlc=plc.get_cost(), + cong=plc.get_congestion_cost(), + cong90=plc.get_congestion_cost_threshold(0.9), + density=plc.get_density_cost(), + fake_net=plc.get_fake_net_cost(), + project=plc.get_project_name(), + block_name=plc.get_block_name(), + hor_routes=hor_routes, + ver_routes=ver_routes, + hor_macro_alloc=hor_macro_alloc, + ver_macro_alloc=ver_macro_alloc, + smooth=smooth, + incr_cost=plc.get_use_incremental_cost(), + overlap_threshold=plc.get_overlap_threshold())) + + info += '\n' + make_blockage_text(plc) + '\n' + info += '\nCounts of node types:\n' + node_type_counts = get_node_type_counts(plc) + for node_type in sorted(node_type_counts): + info += '{:<15} : {:>9}\n'.format(node_type + 's', + node_type_counts[node_type]) + info += '\nHard Macro Placements:\n' + info += get_ascii_picture(get_hard_macro_density_map(plc), cols, rows) + info += '\nOverall Placement Density:\n' + info += get_ascii_picture(plc.get_grid_cells_density(), cols, rows) + info += '\nHorizontal Routing Congestion:\n' + info += get_ascii_picture(plc.get_horizontal_routing_congestion(), cols, rows) + info += '\nVertical Routing Congestion:\n' + info += get_ascii_picture(plc.get_vertical_routing_congestion(), cols, rows) + if user_comments: + info += '\nUser comments:\n' + user_comments + '\n' + info += '\nnode_index x y orientation fixed' + return plc.save_placement(filename, info) + + +def create_placement_cost_using_common_arguments( + netlist_file: str, + init_placement: Optional[str] = None, + canvas_width: Optional[float] = None, + canvas_height: Optional[float] = None, + grid_cols: Optional[int] = None, + grid_rows: Optional[int] = None, + project: Optional[str] = None, + block_name: Optional[str] = None, + congestion_smooth_range: Optional[int] = None, + overlap_threshold: Optional[float] = None, + use_incremental_cost: Optional[bool] = None, + boundary_check: Optional[bool] = None, + blockages: Optional[List[List[float]]] = None, + fix_ports: Optional[bool] = True) -> plc_client.PlacementCost: + """Creates a placement_cost object using the common arguments.""" + if not project: + logging.info('Reading project name from file.') + project = extract_attribute_from_comments('Project', + [init_placement, netlist_file]) + if init_placement and not block_name: + logging.info('Reading block name from file.') + block_name = extract_attribute_from_comments('Block', + [init_placement, netlist_file]) + if not block_name: + logging.warning('block_name is not set. Please add the block_name in:\n%s', + init_placement) + + plc = plc_client.PlacementCost(netlist_file) + # Create blockages. + if blockages is None: + # Try to read blockages from input files. To avoid file I/O, pass blockages, + # or an empty list if there are none. + logging.info('Reading blockages from file.') + for filename in [netlist_file, init_placement]: + if filename is None: + continue + blockages = get_blockages_from_comments(filename) + # Only read blockages from one file. + if blockages: + break + if blockages: + for blockage in blockages: + plc.create_blockage(*blockage) + # Give precedence to command line parameters for canvas/grid sizes. + canvas_size_set = False + if canvas_width and canvas_height: + plc.set_canvas_size(canvas_width, canvas_height) + canvas_size_set = True + grid_size_set = False + if grid_cols and grid_rows: + grid_size_set = True + plc.set_placement_grid(grid_cols, grid_rows) + # Extract and set canvas, grid sizes if they are not already set. + if not canvas_size_set or not grid_size_set: + logging.info('Reading netlist sizes from file.') + for filename in [netlist_file, init_placement]: + if filename is None: + continue + sizes = extract_parameters_from_comments(filename) + canvas_width, canvas_height, grid_cols, grid_rows = sizes + if canvas_width and canvas_height and not canvas_size_set: + plc.set_canvas_size(canvas_width, canvas_height) + if grid_cols and grid_rows and not grid_size_set: + plc.set_placement_grid(grid_cols, grid_rows) + + routing_resources = get_routing_resources() + plc.set_project_name(project or 'unset_project') + plc.set_block_name(block_name or 'unset_block') + plc.set_routes_per_micron(routing_resources['horizontal_routes_per_micron'], + routing_resources['vertical_routes_per_micron']) + plc.set_macro_routing_allocation( + routing_resources['macro_horizontal_routing_allocation'], + routing_resources['macro_vertical_routing_allocation']) + plc.set_congestion_smooth_range(congestion_smooth_range) + plc.set_overlap_threshold(overlap_threshold) + plc.set_canvas_boundary_check(boundary_check) + + # Set macros to initial locations. + if init_placement: + logging.info('Reading init_placement from file %s', init_placement) + # I/O is forbidden in forked child processes. + # Reads init placement from file only if init_locations are not provided. + plc.restore_placement(init_placement) + + if fix_ports: + fix_port_coordinates(plc) + plc.set_use_incremental_cost(use_incremental_cost) + + return plc + + +def get_node_locations(plc: plc_client.PlacementCost) -> Dict[int, int]: + """Returns all node grid locations (macros and stdcells) in a dict.""" + node_locations = dict() + for i in nodes_of_types(plc, ['MACRO', 'STDCELL']): + node_locations[i] = plc.get_grid_cell_of_node(i) + return node_locations + + +def get_node_ordering_by_size(plc: plc_client.PlacementCost) -> List[int]: + """Returns the list of nodes (macros and stdcells) ordered by area.""" + node_areas = dict() + for i in nodes_of_types(plc, ['MACRO', 'STDCELL']): + if plc.is_node_fixed(i): + continue + w, h = plc.get_node_width_height(i) + node_areas[i] = w * h + return sorted(node_areas, key=node_areas.get, reverse=True) + + +def grid_locations_near(plc: plc_client.PlacementCost, + start_grid_index: int) -> Iterator[int]: + """Yields node indices closest to the start_grid_index.""" + # Starting from the start_grid_index, it goes around the area from closest + # (manhattan distance) to the farthest. For example, if the start grid index + # is at 0, the order of the next grid cells will be like: + # 24 + # 22 12 23 + # 20 10 4 11 21 + # 18 8 2 0 3 9 19 + # 16 6 1 7 17 + # 14 5 15 + # 13 + cols, rows = plc.get_grid_num_columns_rows() + start_col, start_row = start_grid_index % cols, int(start_grid_index / cols) + # TODO(mustafay): This may be improved, but it's not crucial now. + for distance in range(cols + rows): + for row_offset in range(-distance, distance + 1): + for col_offset in range(-distance, distance + 1): + if abs(row_offset) + abs(col_offset) != distance: + continue + new_col = start_col + col_offset + new_row = start_row + row_offset + if new_col < 0 or new_row < 0 or new_col >= cols or new_row >= rows: + continue + yield int(new_col + new_row * cols) + + +def place_near(plc: plc_client.PlacementCost, node_index: int, + location: int) -> bool: + """Places a node (legally) closest to the given location. + + Args: + plc: placement_cost object. + node_index: index of a node. + location: target grid cell location. (row * num_cols + num_cols) + + Returns: + True on success, False if this node was not placed on any grid legally. + """ + for loc in grid_locations_near(plc, location): + if plc.can_place_node(node_index, loc): + plc.place_node(node_index, loc) + return True + return False + + +def disconnect_high_fanout_nets(plc: plc_client.PlacementCost, + max_allowed_fanouts: int = 500) -> None: + high_fanout_nets = [] + for i in nodes_of_types(plc, ['PORT', 'STDCELL', 'MACRO_PIN']): + num_fanouts = len(plc.get_fan_outs_of_node(i)) + if num_fanouts > max_allowed_fanouts: + print('Disconnecting node: {} with {} fanouts.'.format( + plc.get_node_name(i), num_fanouts)) + high_fanout_nets.append(i) + plc.disconnect_nets(high_fanout_nets) + + +def legalize_placement(plc: plc_client.PlacementCost) -> bool: + """Places the nodes to legal positions snapping to grid cells.""" + # Unplace all except i/o's. + fix_port_coordinates(plc) + # First save each node's locations on the grid. + # Note that the orientations are not changed by this utility, we do not + # need saving/restoring existing orientations. + node_locations = get_node_locations(plc) + previous_xy_coords = get_node_xy_coordinates(plc) + total_macro_displacement = 0 + total_macros = 0 + plc.unplace_all_nodes() + # Starting with the biggest, place them trying to be as close as possible + # to the original position. + ordered_nodes = get_node_ordering_by_size(plc) + for node in ordered_nodes: + if not place_near(plc, node, node_locations[node]): + print('Could not place node') + return False + if node in previous_xy_coords and not plc.is_node_soft_macro(node): + x, y = plc.get_node_location(node) + px, py = previous_xy_coords[node] + print('x/y displacement: dx = {}, dy = {}, macro: {}'.format( + x - px, y - py, plc.get_node_name(node))) + total_macro_displacement += abs(x - px) + abs(y - py) + total_macros += 1 + print('Total macro displacement: {}, avg: {}'.format( + total_macro_displacement, total_macro_displacement / total_macros)) + return True + +def main(): + """ Run Command: + python3 -m Plc_client.placement_util_os + """ + test_netlist_dir = './Plc_client/test/'+'ariane' + netlist_file = os.path.join(test_netlist_dir,'netlist.pb.txt') + init_placement = os.path.join(test_netlist_dir,'initial.plc') + plc = create_placement_cost(netlist_file=netlist_file, init_placement=init_placement) + # plc.nodes_of_types() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/CodeElements/Plc_client/plc_client_os.py b/CodeElements/Plc_client/plc_client_os.py index 862e84f..cb70a4a 100644 --- a/CodeElements/Plc_client/plc_client_os.py +++ b/CodeElements/Plc_client/plc_client_os.py @@ -85,6 +85,9 @@ class PlacementCost(object): # macro to pins look-up table: [MACRO_NAME] => [PIN_NAME] self.hard_macros_to_inpins = {} self.soft_macros_to_inpins = {} + + # blockage + self.blockages = [] # read netlist self.__read_protobuf() @@ -929,6 +932,12 @@ class PlacementCost(object): """ return sorted(self.hard_macro_indices + self.soft_macro_indices) + def set_project_name(self, project_name): + """ + Set Project name + """ + self.project_name = project_name + def get_project_name(self) -> str: """ Return Project name @@ -1431,7 +1440,12 @@ class PlacementCost(object): """ Return Vertical/Horizontal Macro Allocation """ - return self.modules[node_idx].get_type() + try: + return self.modules_w_pins[node_idx].get_type() + except IndexError: + # NOTE: Google's API return NONE if out of range + print("[INDEX OUT OF RANGE ERROR] Can not process index at {}".format(node_idx)) + return None def make_soft_macros_square(self): pass @@ -1586,9 +1600,6 @@ class PlacementCost(object): def update_node_coords(self): pass - def fix_node_coord(self): - pass - def update_port_sides(self): pass @@ -1613,11 +1624,9 @@ class PlacementCost(object): """In case plc is loaded with fixed macros """ pass - - def fix_node_coord(self): - """Find all ports and fix their coordinates. - """ - pass + + def fix_node_coord(self, node_idx): + self.modules_w_pins[node_idx].set_fix_flag(True) def unplace_all_nodes(self): pass @@ -1646,6 +1655,9 @@ class PlacementCost(object): def get_blockages(self): pass + def create_blockage(self, minx, miny, maxx, maxy, blockage_rate): + self.blockages.append([minx, miny, maxx, maxy, blockage_rate]) + def get_ref_node_id(self, node_idx=-1): """ref_node_id is used for macro_pins. Refers to the macro it belongs to. """ diff --git a/CodeElements/Plc_client/plc_client_os_test.py b/CodeElements/Plc_client/plc_client_os_test.py index c9daa3a..5d92e85 100644 --- a/CodeElements/Plc_client/plc_client_os_test.py +++ b/CodeElements/Plc_client/plc_client_os_test.py @@ -238,6 +238,13 @@ class PlacementCostTest(): self.plc_os.set_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT) self.plc_os.set_placement_grid(self.GRID_COL, self.GRID_ROW) + # TODO: Setting blockage has no effect on proxy cost computation + self.plc.create_blockage(0, 0, 400, 400, 1) + self.plc.create_blockage(0, 0, 200, 200, 1) + print(self.plc.get_blockages()) + print(self.plc.make_soft_macros_square()) + print(self.plc_os.get_soft_macros_count()) + # HPWL try: assert int(self.plc_os.get_wirelength()) == int(self.plc.get_wirelength())