Commit c94d0e7f by sakundu

Merge branch 'main' of github.com:TILOS-AI-Institute/MacroPlacement into flow_scripts

parents a2bf35f2 06d79d2d
...@@ -3,3 +3,9 @@ Flows/*/*/run-* ...@@ -3,3 +3,9 @@ Flows/*/*/run-*
Flows/job Flows/job
Flows/util/__pycache__ Flows/util/__pycache__
CodeElements/*/*/__pycache__ CodeElements/*/*/__pycache__
CodeElements/Plc_client/test/
CodeElements/Plc_client/test/*/*
CodeElements/Plc_client/plc_client_os.py
CodeElements/Plc_client/__pycache__/*
CodeElements/Plc_client/proto_reader.py
CodeElements/Plc_client/plc_client.py
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
### Input: a list of macros (each macro has a width and a height) ### Input: a list of macros (each macro has a width and a height)
### Output: best choice of n_rows and n_cols ### Output: best choice of n_rows and n_cols
import os import os
from math import floor from math import floor
from math import ceil from math import ceil
...@@ -22,6 +21,7 @@ class Grid: ...@@ -22,6 +21,7 @@ class Grid:
self.y_ = y self.y_ = y
self.placed_ = False # if there is macro placed on the center of this grid self.placed_ = False # if there is macro placed on the center of this grid
self.macros_id_ = [] # the id of macros intersecting with this grid self.macros_id_ = [] # the id of macros intersecting with this grid
self.macro_area = 0.0
# Check if there is an overlap with other placed macros # Check if there is an overlap with other placed macros
def CheckOverlap(lx, ly, ux, uy, macro_box): def CheckOverlap(lx, ly, ux, uy, macro_box):
...@@ -33,19 +33,30 @@ def CheckOverlap(lx, ly, ux, uy, macro_box): ...@@ -33,19 +33,30 @@ def CheckOverlap(lx, ly, ux, uy, macro_box):
pass pass
else: else:
return True # there is an overlap return True # there is an overlap
return False return False
# Get overlap area
def GetOverlapArea(box_a, box_b):
box_a_lx, box_a_ly, box_a_ux, box_a_uy = box_a
box_b_lx, box_b_ly, box_b_ux, box_b_uy = box_b
if (box_a_lx >= box_b_ux or box_a_ly >= box_b_uy or box_a_ux <= box_b_lx or box_a_uy <= box_b_ly):
return 0.0
else:
width = min(box_a_ux, box_b_ux) - max(box_a_lx, box_b_lx)
height = min(box_a_uy, box_b_uy) - max(box_a_ly, box_b_ly)
return width * height
# Place macros one by one # Place macros one by one
# n = num_cols # n = num_cols
def PlaceMacros(macro_map, grid_list, chip_width, chip_height, n): def PlaceMacros(macro_map, grid_list, chip_width, chip_height, n):
### All the macro must be placed on the center of one grid ### All the macro must be placed on the center of one grid
#Initialize the position of macros #Initialize the position of macros
ver_sum = 0.0
ver_span_sum = 0.0
hor_sum = 0.0
hor_span_sum = 0.0
macro_bbox = [] macro_bbox = []
# Place macro one by one # Place macro one by one
for key, value in macro_map.items(): for key, value in macro_map.items():
width = value[0] width = value[0]
...@@ -55,7 +66,6 @@ def PlaceMacros(macro_map, grid_list, chip_width, chip_height, n): ...@@ -55,7 +66,6 @@ def PlaceMacros(macro_map, grid_list, chip_width, chip_height, n):
for grid in grid_list: for grid in grid_list:
if (grid.placed_ == True): if (grid.placed_ == True):
continue # this grid has been occupied continue # this grid has been occupied
# if the macro is placed on this # if the macro is placed on this
x = grid.x_ x = grid.x_
y = grid.y_ y = grid.y_
...@@ -67,12 +77,10 @@ def PlaceMacros(macro_map, grid_list, chip_width, chip_height, n): ...@@ -67,12 +77,10 @@ def PlaceMacros(macro_map, grid_list, chip_width, chip_height, n):
# check if the macro is within the outline # check if the macro is within the outline
if (ux > chip_width or uy > chip_height): if (ux > chip_width or uy > chip_height):
continue continue
# check if there is an overlap with other macros # check if there is an overlap with other macros
if (CheckOverlap(lx, ly, ux, uy, macro_bbox) == True): if (CheckOverlap(lx, ly, ux, uy, macro_bbox) == True):
continue continue
# place current macro on this grid # place current macro on this grid
grid.placed_ = True grid.placed_ = True
placed_flag = True placed_flag = True
...@@ -91,22 +99,43 @@ def PlaceMacros(macro_map, grid_list, chip_width, chip_height, n): ...@@ -91,22 +99,43 @@ def PlaceMacros(macro_map, grid_list, chip_width, chip_height, n):
for j in range(min_col_id, max_col_id + 1): for j in range(min_col_id, max_col_id + 1):
grid_id = i * n + j # n is the num_cols grid_id = i * n + j # n is the num_cols
grid_list[grid_id].macros_id_.append(macro_id) grid_list[grid_id].macros_id_.append(macro_id)
grid_box = [i * grid_width, j * grid_height, (i + 1) * grid_width, (j + 1) * grid_height]
overlap_area = GetOverlapArea(grid_box, [lx, ly, ux, uy])
grid_list[grid_id].macro_area += overlap_area
ver_sum += height
ver_span_sum += (max_row_id + 1 - min_row_id) * grid_height
hor_sum += width
hor_span_sum += (max_col_id + 1 - min_col_id) * grid_width
break # stop search remaining candidates break # stop search remaining candidates
# cannot find a valid position for the macro # cannot find a valid position for the macro
if (placed_flag == False): if (placed_flag == False):
return False return False, [0.0, 0.0, 0.0, 0.0]
return True return True, [ver_sum, ver_span_sum, hor_sum, hor_span_sum]
# Define the gridding function # Define the gridding function
def Gridding(macro_width_list, macro_height_list, def Gridding(macro_width_list, macro_height_list,
chip_width, chip_height, tolerance = 0.1, chip_width, chip_height,
min_n_rows = 10, min_n_cols = 10, min_n_rows = 10, min_n_cols = 10,
max_n_rows = 100, max_n_cols = 100, max_n_rows = 128, max_n_cols = 128,
max_rows_times_cols = 3000, min_num_grid_cells = 500,
min_rows_times_cols = 500, max_num_grid_cells = 2500,
max_aspect_ratio = 1.5): max_aspect_ratio = 1.5,
tolerance = 0.05):
"""
Arguments:
macro_width_list, macro_height_list : macro information
chip_width, chip_height : canvas size or core size of the chip
min_n_rows, min_n_cols : mininum number of rows/cols sweep
max_n_rows, max_n_rows : maximum number of rows/cols sweep
min_num_grid_cells, max_num_grid_cells : mininum or maxinum grid cells
max_aspect_ratio : maximum aspect ratio of a grid cell (either w/h or h/w)
tolerance : tolerance to choose lower number of grids
Return:
the best number of rows and cols
"""
### Sort all the macros in a non-decreasing order ### Sort all the macros in a non-decreasing order
if (len(macro_width_list) != len(macro_height_list)): if (len(macro_width_list) != len(macro_height_list)):
print("[Error] The macro information is wrong!!!") print("[Error] The macro information is wrong!!!")
...@@ -116,30 +145,34 @@ def Gridding(macro_width_list, macro_height_list, ...@@ -116,30 +145,34 @@ def Gridding(macro_width_list, macro_height_list,
macro_map = { } macro_map = { }
for i in range(len(macro_width_list)): for i in range(len(macro_width_list)):
macro_map[i] = [macro_width_list[i], macro_height_list[i]] macro_map[i] = [macro_width_list[i], macro_height_list[i]]
macro_map = dict(sorted(macro_map.items(), key=lambda item: item[1][0] * item[1][1], reverse = True)) macro_map = dict(sorted(macro_map.items(), key=lambda item: item[1][0] * item[1][1], reverse = True))
macro_bbox = [] # (lx, ly, ux, uy) for each bounding box
### Print information
print("*"*80) print("*"*80)
print("[INFO] Outline Information : outline_width =", chip_width, " outline_height =", chip_height) print("[INFO] Canvas Information : canvas_width =", chip_width, "canvas_height =", chip_height)
print("\n") print("\n")
print("[INFO] Sorted Macro Information") print("[INFO] Sorted Macro Information")
for key, value in macro_map.items(): for key, value in macro_map.items():
print("macro_" + str(key), " macro_width =", round(value[0], 2), " macro_height =", round(value[1], 2), " macro_area =", round(value[0] * value[1], 2)) line = "macro_" + str(key) + " "
line += "macro_width = " + str(round(value[0], 2)) + " "
line += "macro_height = " + str(round(value[1], 2)) + " "
line += "macro_area = " + str(round(value[0] * value[1], 2))
print(line)
print("\n") print("\n")
### Sweep the n_rows (m) and n_cols (n) in a row-based manner
macro_bbox = [] # (lx, ly, ux, uy) for each bounding box
# we use m for max_n_rows and n for max_n_cols # we use m for max_n_rows and n for max_n_cols
m_best = -1 m_best = -1
n_best = -1 n_best = -1
best_cost = 2.0 # cost should be less than 2.0 based on definition best_metric = -1.0
choice_map = { } choice_map = { } # [m][n] : (ver_cost, hor_cost, empty_ratio)
for m in range(min_n_rows, max_n_rows):
for m in range(min_n_rows, max_n_rows + 1):
choice_map[m] = { } choice_map[m] = { }
for n in range(min_n_cols, max_n_cols + 1): for n in range(min_n_cols, max_n_cols):
if (m * n > max_rows_times_cols): if (m * n > max_num_grid_cells):
break break
if (m * n < min_rows_times_cols): if (m * n < min_num_grid_cells):
continue continue
### Step1: Divide the canvas into grids ### Step1: Divide the canvas into grids
...@@ -148,10 +181,10 @@ def Gridding(macro_width_list, macro_height_list, ...@@ -148,10 +181,10 @@ def Gridding(macro_width_list, macro_height_list,
grid_width = chip_width / n grid_width = chip_width / n
if (grid_height / grid_width > max_aspect_ratio): if (grid_height / grid_width > max_aspect_ratio):
continue continue
if (grid_width / grid_height > max_aspect_ratio): if (grid_width / grid_height > max_aspect_ratio):
continue continue
### Step2: Try to place macros on canvas
grid_list = [] grid_list = []
for i in range(m): for i in range(m):
for j in range(n): for j in range(n):
...@@ -160,24 +193,24 @@ def Gridding(macro_width_list, macro_height_list, ...@@ -160,24 +193,24 @@ def Gridding(macro_width_list, macro_height_list,
grid_id = len(grid_list) grid_id = len(grid_list)
grid_list.append(Grid(grid_id, grid_width, grid_height, x, y)) grid_list.append(Grid(grid_id, grid_width, grid_height, x, y))
value = [0.0, 0.0, 0.0, 0.0]
### Place macros one by one ### Place macros one by one
if (PlaceMacros(macro_map, grid_list, chip_width, chip_height, n) == False): result_flag, value = PlaceMacros(macro_map, grid_list, chip_width, chip_height, n)
if (result_flag == False):
continue continue
else: else:
### Calculate the cost ### compute the empty ratio
total_grid_width = 0.0 used_threshold = 1e-5
total_grid_height = 0.0 num_empty_grids = 0
for grid in grid_list: for grid in grid_list:
if (len(grid.macros_id_) > 0): if (grid.macro_area / (grid_width * grid_height) < used_threshold):
total_grid_width += grid.width_ num_empty_grids += 1
total_grid_height += grid.height_ metric = 1.0 - value[0] / value[1]
metric += 1.0 - value[2] / value[3]
# calculate h_cost metric += num_empty_grids / len(grid_list)
cost = 1.0 - sum(macro_width_list) / total_grid_width choice_map[m][n] = metric
cost += 1.0 - sum(macro_height_list) / total_grid_height if (metric > best_metric):
choice_map[m][n] = cost best_metric = metric
if (cost < best_cost):
best_cost = cost
m_best = m m_best = m
n_best = n n_best = n
m_opt = m_best m_opt = m_best
...@@ -188,9 +221,9 @@ def Gridding(macro_width_list, macro_height_list, ...@@ -188,9 +221,9 @@ def Gridding(macro_width_list, macro_height_list,
print("n_best = ", n_best) print("n_best = ", n_best)
print("tolerance = ", tolerance) print("tolerance = ", tolerance)
for [m, m_map] in choice_map.items(): for [m, m_map] in choice_map.items():
for [n, cost] in m_map.items(): for [n, metric] in m_map.items():
print("m = ", m , " n = ", n, " cost = ", cost) print("m = ", m , " n = ", n, " metric = ", metric)
if ((cost <= (1.0 + tolerance) * best_cost) and (m * n < num_grids_opt)): if ((metric >= (1.0 - tolerance) * best_metric) and (m * n < num_grids_opt)):
m_opt = m m_opt = m
n_opt = n n_opt = n
num_grids_opt = m * n num_grids_opt = m * n
...@@ -201,7 +234,7 @@ def Gridding(macro_width_list, macro_height_list, ...@@ -201,7 +234,7 @@ def Gridding(macro_width_list, macro_height_list,
class GriddingLefDefInterface: class GriddingLefDefInterface:
def __init__(self, src_dir, design, setup_file = "setup.tcl", tolerance = 0.05, def __init__(self, src_dir, design, setup_file = "setup.tcl", tolerance = 0.05,
halo_width = 5.0, min_n_rows = 10, min_n_cols = 10, max_n_rows = 128, halo_width = 0.0, min_n_rows = 10, min_n_cols = 10, max_n_rows = 128,
max_n_cols = 128, max_rows_times_cols = 2500, min_rows_times_cols = 500, max_n_cols = 128, max_rows_times_cols = 2500, min_rows_times_cols = 500,
max_aspect_ratio = 1.5): max_aspect_ratio = 1.5):
self.src_dir = src_dir self.src_dir = src_dir
...@@ -227,11 +260,11 @@ class GriddingLefDefInterface: ...@@ -227,11 +260,11 @@ class GriddingLefDefInterface:
self.GenerateHypergraph() self.GenerateHypergraph()
self.ExtractInputs() self.ExtractInputs()
self.m_opt, self.n_opt = Gridding(self.macro_width_list, self.macro_height_list, self.m_opt, self.n_opt = Gridding(self.macro_width_list, self.macro_height_list,
self.chip_width, self.chip_height, self.tolerance, self.chip_width, self.chip_height,
self.min_n_rows, self.min_n_cols, self.min_n_rows, self.min_n_cols,
self.max_n_rows, self.max_n_cols, self.max_n_rows, self.max_n_cols,
self.max_rows_times_cols, self.min_rows_times_cols, self.min_rows_times_cols, self.max_rows_times_cols,
self.max_aspect_ratio) self.max_aspect_ratio, self.tolerance)
def GetNumRows(self): def GetNumRows(self):
return self.m_opt return self.m_opt
...@@ -278,6 +311,7 @@ class GriddingLefDefInterface: ...@@ -278,6 +311,7 @@ class GriddingLefDefInterface:
f.close() f.close()
items = content[0].split() items = content[0].split()
print(items)
self.chip_width = float(items[2]) - float(items[0]) self.chip_width = float(items[2]) - float(items[0])
self.chip_height = float(items[3]) - float(items[1]) self.chip_height = float(items[3]) - float(items[1])
......
...@@ -9,7 +9,20 @@ curl 'https://raw.githubusercontent.com/google-research/circuit_training/main/ci ...@@ -9,7 +9,20 @@ curl 'https://raw.githubusercontent.com/google-research/circuit_training/main/ci
sudo curl https://storage.googleapis.com/rl-infra-public/circuit-training/placement_cost/plc_wrapper_main \ sudo curl https://storage.googleapis.com/rl-infra-public/circuit-training/placement_cost/plc_wrapper_main \
-o /usr/local/bin/plc_wrapper_main -o /usr/local/bin/plc_wrapper_main
# Run plc testbench # Run plc testbench
python -m Plc_client.plc_client_os_test # python -m Plc_client.plc_client_os_test [-h] [--helpfull] --netlist NETLIST [--plc PLC] --width WIDTH --height HEIGHT --col COL --row ROW [--rpmh RPMH] [--rpmv RPMV] [--marh MARH] [--marv MARV] [--smooth SMOOTH]
# Example
python -m Plc_client.plc_client_os_test --netlist ./Plc_client/test/ariane/netlist.pb.txt\
--plc ./Plc_client/test/ariane/initial.plc\
--width 356.592\
--height 356.640\
--col 35\
--row 33\
--rpmh 10\
--rpmv 10\
--marh 5\
--marv 5\
--smooth 2
``` ```
## HPWL Computation ## HPWL Computation
...@@ -27,15 +40,15 @@ $$ ...@@ -27,15 +40,15 @@ $$
HPWL(netlist) = \sum_{i}^{N_{netlist}} W_{i\_{source}} \cdot [max_{b\in i}(x_b) - min_{b\in i}(x_b) + max_{b\in i}(y_b) - min_{b\in i}(y_b)] HPWL(netlist) = \sum_{i}^{N_{netlist}} W_{i\_{source}} \cdot [max_{b\in i}(x_b) - min_{b\in i}(x_b) + max_{b\in i}(y_b) - min_{b\in i}(y_b)]
$$ $$
## Density Cost Computation ## Density Cost Computation
Density cost is computed from grid cells density. Density cost is computed from grid cells density.
By default, any given input will have grid col/row set to 10/10 until user later defines in the .plc file. By default, any given input will have grid col/row set to 10/10 until user later defines in the .plc file.
Grid cell density is represented as an 1D array where the length is set to be Grid cell density is represented as an 1D array where the length is set to be the following:
$$ $$
grid_\{col} \cdot grid_\{row} grid_{col} \cdot grid_{row}
$$ $$
Each entry of this array represents the current occupied precentage within this cell. Each entry of this array represents the current occupied precentage within this cell.
......
# 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
# done
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
#done
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
#done
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 = create_placement_cost_using_common_arguments(netlist_file=netlist_file, init_placement=init_placement,
grid_cols=10, grid_rows=10, congestion_smooth_range=2.0, overlap_threshold=0.004, use_incremental_cost=False)
# plc.nodes_of_types()
if __name__ == '__main__':
main()
\ No newline at end of file
...@@ -9,6 +9,24 @@ from collections import namedtuple ...@@ -9,6 +9,24 @@ from collections import namedtuple
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle from matplotlib.patches import Rectangle
import numpy as np import numpy as np
import traceback, sys
"""plc_client_os docstrings.
Open-sourced effort for plc_client and Google's API, plc_wrapper_main. This module
is used to initialize a PlacementCost object that computes the meta-information and
proxy cost function for RL agent's reward signal at the end of each placement.
Example:
For testing, please refer to plc_client_os_test.py for more information.
Todo:
* Add Documentation
* Clean up
* location information update not correctly after restore placement
* test if cell < 5, congestion cost computation
"""
Block = namedtuple('Block', 'x_max y_max x_min y_min') Block = namedtuple('Block', 'x_max y_max x_min y_min')
...@@ -25,6 +43,13 @@ class PlacementCost(object): ...@@ -25,6 +43,13 @@ class PlacementCost(object):
self.macro_macro_x_spacing = macro_macro_x_spacing self.macro_macro_x_spacing = macro_macro_x_spacing
self.macro_macro_y_spacing = macro_macro_y_spacing self.macro_macro_y_spacing = macro_macro_y_spacing
# Update flags
self.FLAG_UPDATE_WIRELENGTH = True
self.FLAG_UPDATE_DENSITY = True
self.FLAG_UPDATE_CONGESTION = True
self.FLAG_UPDATE_MACRO_ADJ = True
self.FLAG_UPDATE_MACRO_AND_CLUSTERED_PORT_ADJ = True
# Check netlist existance # Check netlist existance
assert os.path.isfile(self.netlist_file) assert os.path.isfile(self.netlist_file)
...@@ -38,9 +63,9 @@ class PlacementCost(object): ...@@ -38,9 +63,9 @@ class PlacementCost(object):
self.overlap_thres = 0.0 self.overlap_thres = 0.0
self.hrouting_alloc = 0.0 self.hrouting_alloc = 0.0
self.vrouting_alloc = 0.0 self.vrouting_alloc = 0.0
self.macro_horizontal_routing_allocation = 0.0 self.macro_horizontal_routing_allocation = 0.0
self.macro_vertical_routing_allocation = 0.0 self.macro_vertical_routing_allocation = 0.0
self.canvas_boundary_check = True
# net information # net information
self.net_cnt = 0 self.net_cnt = 0
...@@ -60,6 +85,11 @@ class PlacementCost(object): ...@@ -60,6 +85,11 @@ class PlacementCost(object):
# macro to pins look-up table: [MACRO_NAME] => [PIN_NAME] # macro to pins look-up table: [MACRO_NAME] => [PIN_NAME]
self.hard_macros_to_inpins = {} self.hard_macros_to_inpins = {}
self.soft_macros_to_inpins = {} self.soft_macros_to_inpins = {}
# unknown
self.use_incremental_cost = False
# blockage
self.blockages = []
# read netlist # read netlist
self.__read_protobuf() self.__read_protobuf()
...@@ -78,16 +108,16 @@ class PlacementCost(object): ...@@ -78,16 +108,16 @@ class PlacementCost(object):
# initial grid mask # initial grid mask
self.global_node_mask = [0] * self.grid_col * self.grid_row self.global_node_mask = [0] * self.grid_col * self.grid_row
# store module/component count # store module/component count
self.port_cnt = len(self.port_indices) self.ports_cnt = len(self.port_indices)
self.hard_macro_cnt = len(self.hard_macro_indices) self.hard_macro_cnt = len(self.hard_macro_indices)
self.hard_macro_pin_cnt = len(self.hard_macro_pin_indices) self.hard_macro_pins_cnt = len(self.hard_macro_pin_indices)
self.soft_macro_cnt = len(self.soft_macro_indices) self.soft_macros_cnt = len(self.soft_macro_indices)
self.soft_macro_pin_cnt = len(self.soft_macro_pin_indices) self.soft_macro_pins_cnt = len(self.soft_macro_pin_indices)
self.module_cnt = self.hard_macro_cnt + self.soft_macro_cnt + self.port_cnt self.module_cnt = self.hard_macro_cnt + self.soft_macros_cnt + self.ports_cnt
# assert module and pin count are correct # assert module and pin count are correct
assert (len(self.modules)) == self.module_cnt assert (len(self.modules)) == self.module_cnt
assert (len(self.modules_w_pins) - \ assert (len(self.modules_w_pins) - \
self.hard_macro_pin_cnt - self.soft_macro_pin_cnt) \ self.hard_macro_pins_cnt - self.soft_macro_pins_cnt) \
== self.module_cnt == self.module_cnt
def __peek(self, f:io.TextIOWrapper): def __peek(self, f:io.TextIOWrapper):
...@@ -185,12 +215,12 @@ class PlacementCost(object): ...@@ -185,12 +215,12 @@ class PlacementCost(object):
try: try:
assert 'x' in attr_dict.keys() assert 'x' in attr_dict.keys()
except AssertionError: except AssertionError:
logging.warning('x is not defined') logging.warning('[NETLIST PARSER ERROR] x is not defined')
try: try:
assert 'y' in attr_dict.keys() assert 'y' in attr_dict.keys()
except AssertionError: except AssertionError:
logging.warning('y is not defined') logging.warning('[NETLIST PARSER ERROR] y is not defined')
soft_macro = self.SoftMacro(name=node_name, width=attr_dict['width'][1], soft_macro = self.SoftMacro(name=node_name, width=attr_dict['width'][1],
height = attr_dict['height'][1], height = attr_dict['height'][1],
...@@ -317,30 +347,213 @@ class PlacementCost(object): ...@@ -317,30 +347,213 @@ class PlacementCost(object):
# mapping connection degree to each macros # mapping connection degree to each macros
self.__update_connection() self.__update_connection()
def __read_plc(self): def __read_plc(self, plc_pth: str):
""" """
Plc file Parser Plc file Parser
""" """
with open(self.init_plc) as fp: # meta information
line = fp.readline() _columns = 0
_rows = 0
while line: _width = 0.0
# skip comments _height = 0.0
if re.search(r"\S", line)[0] == '#': _area = 0.0
# IMPORTANT: Advance pt _block = None
line = fp.readline() _routes_per_micron_hor = 0.0
continue _routes_per_micron_ver = 0.0
_routes_used_by_macros_hor = 0.0
# words itemize into list _routes_used_by_macros_ver = 0.0
line_item = re.findall(r'[0-9A-Za-z\.\-]+', line) _smoothing_factor = 0
_overlap_threshold = 0.0
# skip empty lines
if len(line_item) == 0: # node information
# IMPORTANT: Advance pt _hard_macros_cnt = 0
line = fp.readline() _hard_macro_pins_cnt = 0
continue _macros_cnt = 0
_macro_pin_cnt = 0
line = fp.readline() _ports_cnt = 0
_soft_macros_cnt = 0
_soft_macro_pins_cnt = 0
_stdcells_cnt = 0
# node placement
_node_plc = {}
for cnt, line in enumerate(open(plc_pth, 'r')):
line_item = re.findall(r'[0-9A-Za-z\.\-]+', line)
# skip empty lines
if len(line_item) == 0:
continue
if 'Columns' in line_item and 'Rows' in line_item:
# Columns and Rows should be defined on the same one-line
_columns = int(line_item[1])
_rows = int(line_item[3])
elif "Width" in line_item and "Height" in line_item:
# Width and Height should be defined on the same one-line
_width = float(line_item[1])
_height = float(line_item[3])
elif "Area" in line_item:
# Total core area of modules
_area = float(line_item[1])
elif "Block" in line_item:
# The block name of the testcase
_block = str(line_item[1])
elif all(it in line_item for it in\
['Routes', 'per', 'micron', 'hor', 'ver']):
# For routing congestion computation
_routes_per_micron_hor = float(line_item[4])
_routes_per_micron_ver = float(line_item[6])
elif all(it in line_item for it in\
['Routes', 'used', 'by', 'macros', 'hor', 'ver']):
# For MACRO congestion computation
_routes_used_by_macros_hor = float(line_item[5])
_routes_used_by_macros_ver = float(line_item[7])
elif all(it in line_item for it in ['Smoothing', 'factor']):
# smoothing factor for routing congestion
_smoothing_factor = int(line_item[2])
elif all(it in line_item for it in ['Overlap', 'threshold']):
# overlap
_overlap_threshold = float(line_item[2])
elif all(it in line_item for it in ['HARD', 'MACROs'])\
and len(line_item) == 3:
_hard_macros_cnt = int(line_item[2])
elif all(it in line_item for it in ['HARD', 'MACRO', 'PINs'])\
and len(line_item) == 4:
_hard_macro_pins_cnt = int(line_item[3])
elif all(it in line_item for it in ['PORTs'])\
and len(line_item) == 2:
_ports_cnt = int(line_item[1])
elif all(it in line_item for it in ['SOFT', 'MACROs'])\
and len(line_item) == 3:
_soft_macros_cnt = int(line_item[2])
elif all(it in line_item for it in ['SOFT', 'MACRO', 'PINs'])\
and len(line_item) == 4:
_soft_macro_pins_cnt = int(line_item[3])
elif all(it in line_item for it in ['STDCELLs'])\
and len(line_item) == 2:
_stdcells_cnt = int(line_item[1])
elif all(it in line_item for it in ['MACROs'])\
and len(line_item) == 2:
_macros_cnt = int(line_item[1])
elif all(re.match(r'[0-9NEWS\.\-]+', it) for it in line_item)\
and len(line_item) == 5:
# [node_index] [x] [y] [orientation] [fixed]
_node_plc[int(line_item[0])] = line_item[1:]
# return as dictionary
info_dict = { "columns":_columns,
"rows":_rows,
"width":_width,
"height":_height,
"area":_area,
"block":_block,
"routes_per_micron_hor":_routes_per_micron_hor,
"routes_per_micron_ver":_routes_per_micron_ver,
"routes_used_by_macros_hor":_routes_used_by_macros_hor,
"routes_used_by_macros_ver":_routes_used_by_macros_ver,
"smoothing_factor":_smoothing_factor,
"overlap_threshold":_overlap_threshold,
"hard_macros_cnt":_hard_macros_cnt,
"hard_macro_pins_cnt":_hard_macro_pins_cnt,
"macros_cnt":_macros_cnt,
"macro_pin_cnt":_macro_pin_cnt,
"ports_cnt":_ports_cnt,
"soft_macros_cnt":_soft_macros_cnt,
"soft_macro_pins_cnt":_soft_macro_pins_cnt,
"stdcells_cnt":_stdcells_cnt,
"node_plc":_node_plc
}
return info_dict
def restore_placement(self, plc_pth: str, ifInital=True, ifValidate=False, ifReadComment = False):
"""
Read and retrieve .plc file information
NOTE: DO NOT always set self.init_plc because this function is also
used to read final placement file.
ifReadComment: By default, Google's plc_client does not extract
information from .plc comment. This is purely done in
placement_util.py. For purpose of testing, we included this option.
"""
# if plc is an initial placement
if ifInital:
self.init_plc = plc_pth
# recompute cost from new location
self.FLAG_UPDATE_CONGESTION = True
self.FLAG_UPDATE_DENSITY = True
self.FLAG_UPDATE_WIRELENGTH = True
# extracted information from .plc file
info_dict = self.__read_plc(plc_pth)
# validate netlist.pb.txt is on par with .plc
if ifValidate:
try:
assert(self.hard_macro_cnt == info_dict['hard_macros_cnt'])
assert(self.hard_macro_pins_cnt == info_dict['hard_macro_pins_cnt'])
assert(self.soft_macros_cnt == info_dict['soft_macros_cnt'])
assert(self.soft_macro_pins_cnt == info_dict['soft_macro_pins_cnt'])
assert(self.ports_cnt == info_dict['ports_cnt'])
except AssertionError:
_, _, tb = sys.exc_info()
traceback.print_tb(tb)
tb_info = traceback.extract_tb(tb)
_, line, _, text = tb_info[-1]
print('[NETLIST/PLC MISMATCH ERROR] at line {} in statement {}'\
.format(line, text))
exit(1)
# restore placement for each module
try:
assert sorted(self.port_indices +\
self.hard_macro_indices +\
self.soft_macro_indices) == list(info_dict['node_plc'].keys())
except AssertionError:
print('[PLC INDICES MISMATCH ERROR]', len(sorted(self.port_indices +\
self.hard_macro_indices +\
self.soft_macro_indices)), len(list(info_dict['node_plc'].keys())))
exit(1)
for mod_idx in info_dict['node_plc'].keys():
mod_x = mod_y = mod_orient = mod_ifFixed = None
try:
mod_x = float(info_dict['node_plc'][mod_idx][0])
mod_y = float(info_dict['node_plc'][mod_idx][1])
mod_orient = info_dict['node_plc'][mod_idx][2]
mod_ifFixed = int(info_dict['node_plc'][mod_idx][3])
except Exception as e:
print('[PLC PARSER ERROR] %s' % str(e))
#TODO ValueError: Error in calling RestorePlacement with ('./Plc_client/test/ariane/initial.plc',): Can't place macro i_ariane/i_frontend/i_icache/sram_block_3__tag_sram/mem/mem_inst_mem_256x45_256x16_0x0 at (341.75, 8.8835). Exceeds the boundaries of the placement area..
self.modules_w_pins[mod_idx].set_pos(mod_x, mod_y)
if mod_orient and mod_orient != '-':
self.modules_w_pins[mod_idx].set_orientation(mod_orient)
if mod_ifFixed == 0:
self.modules_w_pins[mod_idx].set_fix_flag(False)
elif mod_ifFixed == 1:
self.modules_w_pins[mod_idx].set_fix_flag(True)
# set meta information
if ifReadComment:
self.set_canvas_size(info_dict['width'], info_dict['height'])
self.set_placement_grid(info_dict['columns'], info_dict['rows'])
self.set_block_name(info_dict['block'])
self.set_routes_per_micron(
info_dict['routes_per_micron_hor'],
info_dict['routes_per_micron_ver']
)
self.set_macro_routing_allocation(
info_dict['routes_used_by_macros_hor'],
info_dict['routes_used_by_macros_ver']
)
self.set_congestion_smooth_range(info_dict['smoothing_factor'])
self.set_overlap_threshold(info_dict['overlap_threshold'])
def __update_connection(self): def __update_connection(self):
""" """
...@@ -371,17 +584,15 @@ class PlacementCost(object): ...@@ -371,17 +584,15 @@ class PlacementCost(object):
if macro_type == "MACRO" or macro_type == "macro": if macro_type == "MACRO" or macro_type == "macro":
weight = pin.get_weight() weight = pin.get_weight()
macro.add_connections(inputs[k], weight) macro.add_connections(inputs[k], weight)
def __update_placement(self):
# assign modules to grid cells
pass
def get_cost(self) -> float: def get_cost(self) -> float:
""" """
Compute wirelength cost from wirelength Compute wirelength cost from wirelength
""" """
return self.get_wirelength() / ((self.get_canvas_width_height()[0] + self.get_canvas_width_height()[1]) * self.net_cnt) if self.FLAG_UPDATE_WIRELENGTH:
self.FLAG_UPDATE_WIRELENGTH = False
return self.get_wirelength() / ((self.get_canvas_width_height()[0]\
+ self.get_canvas_width_height()[1]) * self.net_cnt)
def get_area(self) -> float: def get_area(self) -> float:
""" """
...@@ -393,20 +604,20 @@ class PlacementCost(object): ...@@ -393,20 +604,20 @@ class PlacementCost(object):
total_area += mod.get_area() total_area += mod.get_area()
return total_area return total_area
def get_hard_macro_count(self) -> int: def get_hard_macros_count(self) -> int:
return self.hard_macro_cnt return self.hard_macro_cnt
def get_port_count(self) -> int: def get_ports_count(self) -> int:
return self.port_cnt return self.ports_cnt
def get_soft_macro_count(self) -> int: def get_soft_macros_count(self) -> int:
return self.soft_macro_cnt return self.soft_macros_cnt
def get_hard_macro_pin_count(self) -> int: def get_hard_macro_pins_count(self) -> int:
return self.hard_macro_pin_cnt return self.hard_macro_pins_cnt
def get_soft_macro_pin_count(self) -> int: def get_soft_macro_pins_count(self) -> int:
return self.soft_macro_pin_cnt return self.soft_macro_pins_cnt
def get_wirelength(self) -> float: def get_wirelength(self) -> float:
""" """
...@@ -521,33 +732,15 @@ class PlacementCost(object): ...@@ -521,33 +732,15 @@ class PlacementCost(object):
def get_congestion_cost(self): def get_congestion_cost(self):
#return max(self.get_H_congestion_cost(), self.get_V_congestion_cost()) #return max(self.get_H_congestion_cost(), self.get_V_congestion_cost())
# TODO need to test if cong is smaller than 5
return self.abu(self.V_routing_cong + self.H_routing_cong, 0.05) return self.abu(self.V_routing_cong + self.H_routing_cong, 0.05)
# temp_cong = [sum(x) for x in zip(self.V_routing_cong, self.H_routing_cong)]
# occupied_cells = sorted([gc for gc in temp_cong if gc != 0.0], reverse=True)
# cong_cost = 0.0
# # take top 10%
# cong_cnt = math.floor(len(temp_cong) * 0.1)
# # if grid cell smaller than 10, take the average over occupied cells
# if len(temp_cong) < 10:
# cong_cost = float(sum(occupied_cells) / len(occupied_cells))
# return cong_cost
# idx = 0
# sum_cong = 0
# # take top 10%
# while idx < cong_cnt and idx < len(occupied_cells):
# sum_cong += occupied_cells[idx]
# idx += 1
# return float(sum_cong / cong_cnt)
def __get_grid_cell_location(self, x_pos, y_pos): def __get_grid_cell_location(self, x_pos, y_pos):
""" """
private function for getting grid cell row/col ranging from 0...N private function for getting grid cell row/col ranging from 0...N
""" """
self.grid_width = float(self.width/self.grid_col)
self.grid_height = float(self.height/self.grid_row)
row = math.floor(y_pos / self.grid_height) row = math.floor(y_pos / self.grid_height)
col = math.floor(x_pos / self.grid_width) col = math.floor(x_pos / self.grid_width)
return row, col return row, col
...@@ -661,6 +854,10 @@ class PlacementCost(object): ...@@ -661,6 +854,10 @@ class PlacementCost(object):
""" """
compute average of top 10% of grid cell density and take half of it compute average of top 10% of grid cell density and take half of it
""" """
if self.FLAG_UPDATE_DENSITY:
self.get_grid_cells_density()
self.FLAG_UPDATE_DENSITY=False
occupied_cells = sorted([gc for gc in self.grid_cells if gc != 0.0], reverse=True) occupied_cells = sorted([gc for gc in self.grid_cells if gc != 0.0], reverse=True)
density_cost = 0.0 density_cost = 0.0
...@@ -688,6 +885,11 @@ class PlacementCost(object): ...@@ -688,6 +885,11 @@ class PlacementCost(object):
self.width = width self.width = width
self.height = height self.height = height
# Flag updates
self.FLAG_UPDATE_CONGESTION = True
self.FLAG_UPDATE_DENSITY = True
self.FLAG_UPDATE_MACRO_AND_CLUSTERED_PORT_ADJ = True
self.grid_width = float(self.width/self.grid_col) self.grid_width = float(self.width/self.grid_col)
self.grid_height = float(self.height/self.grid_row) self.grid_height = float(self.height/self.grid_row)
return True return True
...@@ -702,9 +904,15 @@ class PlacementCost(object): ...@@ -702,9 +904,15 @@ class PlacementCost(object):
""" """
Set grid col/row Set grid col/row
""" """
print("#[PLACEMENT GRID] Col: %d, Row: %d" % (grid_col, grid_row))
self.grid_col = grid_col self.grid_col = grid_col
self.grid_row = grid_row self.grid_row = grid_row
# Flag updates
self.FLAG_UPDATE_CONGESTION = True
self.FLAG_UPDATE_DENSITY = True
self.FLAG_UPDATE_MACRO_AND_CLUSTERED_PORT_ADJ = True
self.V_routing_cong = [0] * self.grid_col * self.grid_row self.V_routing_cong = [0] * self.grid_col * self.grid_row
self.H_routing_cong = [0] * self.grid_col * self.grid_row self.H_routing_cong = [0] * self.grid_col * self.grid_row
self.V_macro_routing_cong = [0] * self.grid_col * self.grid_row self.V_macro_routing_cong = [0] * self.grid_col * self.grid_row
...@@ -726,11 +934,23 @@ class PlacementCost(object): ...@@ -726,11 +934,23 @@ class PlacementCost(object):
""" """
return sorted(self.hard_macro_indices + self.soft_macro_indices) 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: def get_project_name(self) -> str:
""" """
Return Project name Return Project name
""" """
return self.project_name return self.project_name
def set_block_name(self, block_name:str) -> None:
"""
Return Block name
"""
self.block_name = block_name
def get_block_name(self) -> str: def get_block_name(self) -> str:
""" """
...@@ -742,6 +962,10 @@ class PlacementCost(object): ...@@ -742,6 +962,10 @@ class PlacementCost(object):
""" """
Set Routes per Micron Set Routes per Micron
""" """
print("#[ROUTES PER MICRON] Hor: %.2f, Ver: %.2f" % (hroutes_per_micron, vroutes_per_micron))
# Flag updates
self.FLAG_UPDATE_CONGESTION = True
self.hroutes_per_micron = hroutes_per_micron self.hroutes_per_micron = hroutes_per_micron
self.vroutes_per_micron = vroutes_per_micron self.vroutes_per_micron = vroutes_per_micron
...@@ -755,7 +979,11 @@ class PlacementCost(object): ...@@ -755,7 +979,11 @@ class PlacementCost(object):
""" """
Set congestion smooth range Set congestion smooth range
""" """
self.smooth_range = int(smooth_range) print("#[CONGESTION SMOOTH RANGE] Smooth Range: %d" % (smooth_range))
# Flag updates
self.FLAG_UPDATE_CONGESTION = True
self.smooth_range = math.floor(smooth_range)
def get_congestion_smooth_range(self) -> float: def get_congestion_smooth_range(self) -> float:
""" """
...@@ -767,6 +995,7 @@ class PlacementCost(object): ...@@ -767,6 +995,7 @@ class PlacementCost(object):
""" """
Set Overlap Threshold Set Overlap Threshold
""" """
print("#[OVERLAP THRESHOLD] Threshold: %.4f" % (overlap_thres))
self.overlap_thres = overlap_thres self.overlap_thres = overlap_thres
def get_overlap_threshold(self) -> float: def get_overlap_threshold(self) -> float:
...@@ -775,13 +1004,25 @@ class PlacementCost(object): ...@@ -775,13 +1004,25 @@ class PlacementCost(object):
""" """
return self.overlap_thres return self.overlap_thres
def set_canvas_boundary_check(self, ifCheck:bool) -> None:
"""
boundary_check: Do a boundary check during node placement.
"""
self.canvas_boundary_check = ifCheck
def get_canvas_boundary_check(self) -> bool: def get_canvas_boundary_check(self) -> bool:
return False """
return canvas_boundary_check
"""
return self.canvas_boundary_check
def set_macro_routing_allocation(self, hrouting_alloc:float, vrouting_alloc:float) -> None: def set_macro_routing_allocation(self, hrouting_alloc:float, vrouting_alloc:float) -> None:
""" """
Set Vertical/Horizontal Macro Allocation Set Vertical/Horizontal Macro Allocation
""" """
# Flag updates
self.FLAG_UPDATE_CONGESTION = True
self.hrouting_alloc = hrouting_alloc self.hrouting_alloc = hrouting_alloc
self.vrouting_alloc = vrouting_alloc self.vrouting_alloc = vrouting_alloc
...@@ -915,7 +1156,6 @@ class PlacementCost(object): ...@@ -915,7 +1156,6 @@ class PlacementCost(object):
self.t_routing(temp_gcell, weight) self.t_routing(temp_gcell, weight)
return return
def __macro_route_over_grid_cell(self, mod_x, mod_y, mod_w, mod_h): def __macro_route_over_grid_cell(self, mod_x, mod_y, mod_w, mod_h):
""" """
private function for add module to grid cells private function for add module to grid cells
...@@ -1019,29 +1259,38 @@ class PlacementCost(object): ...@@ -1019,29 +1259,38 @@ class PlacementCost(object):
def get_vertical_routing_congestion(self): def get_vertical_routing_congestion(self):
# TODO: detect if we need to run # TODO: detect if we need to run
self.get_routing() if self.FLAG_UPDATE_CONGESTION:
self.get_routing()
return self.V_routing_cong return self.V_routing_cong
def get_horizontal_routing_congestion(self): def get_horizontal_routing_congestion(self):
# TODO: detect if we need to run # TODO: detect if we need to run
self.get_routing() if self.FLAG_UPDATE_CONGESTION:
self.get_routing()
return self.H_routing_cong return self.H_routing_cong
def get_routing(self): def get_routing(self):
self.grid_width = float(self.width/self.grid_col) """
self.grid_height = float(self.height/self.grid_row) Route between modules
"""
if self.FLAG_UPDATE_CONGESTION:
self.grid_width = float(self.width/self.grid_col)
self.grid_height = float(self.height/self.grid_row)
self.grid_v_routes = self.grid_width * self.vroutes_per_micron self.grid_v_routes = self.grid_width * self.vroutes_per_micron
self.grid_h_routes = self.grid_height * self.hroutes_per_micron self.grid_h_routes = self.grid_height * self.hroutes_per_micron
# reset grid # reset grid
self.H_routing_cong = [0] * self.grid_row * self.grid_col self.H_routing_cong = [0] * self.grid_row * self.grid_col
self.V_routing_cong = [0] * self.grid_row * self.grid_col self.V_routing_cong = [0] * self.grid_row * self.grid_col
self.H_macro_routing_cong = [0] * self.grid_row * self.grid_col self.H_macro_routing_cong = [0] * self.grid_row * self.grid_col
self.V_macro_routing_cong = [0] * self.grid_row * self.grid_col self.V_macro_routing_cong = [0] * self.grid_row * self.grid_col
self.FLAG_UPDATE_CONGESTION = False
net_count = 0
for mod in self.modules_w_pins: for mod in self.modules_w_pins:
norm_fact = 1.0 norm_fact = 1.0
curr_type = mod.get_type() curr_type = mod.get_type()
...@@ -1117,8 +1366,7 @@ class PlacementCost(object): ...@@ -1117,8 +1366,7 @@ class PlacementCost(object):
# sum up routing congestion with macro congestion # sum up routing congestion with macro congestion
self.V_routing_cong = [sum(x) for x in zip(self.V_routing_cong, self.V_macro_routing_cong)] self.V_routing_cong = [sum(x) for x in zip(self.V_routing_cong, self.V_macro_routing_cong)]
self.H_routing_cong = [sum(x) for x in zip(self.H_routing_cong, self.H_macro_routing_cong)] self.H_routing_cong = [sum(x) for x in zip(self.H_routing_cong, self.H_macro_routing_cong)]
def __smooth_routing_cong(self): def __smooth_routing_cong(self):
temp_V_routing_cong = [0] * self.grid_col * self.grid_row temp_V_routing_cong = [0] * self.grid_col * self.grid_row
temp_H_routing_cong = [0] * self.grid_col * self.grid_row temp_H_routing_cong = [0] * self.grid_col * self.grid_row
...@@ -1194,20 +1442,36 @@ class PlacementCost(object): ...@@ -1194,20 +1442,36 @@ class PlacementCost(object):
""" """
Return Vertical/Horizontal Macro Allocation 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 WARNING] Can not process index at {}".format(node_idx))
return None
def make_soft_macros_square(self): def make_soft_macros_square(self):
pass pass
def set_use_incremental_cost(self, use_incremental_cost):
self.use_incremental_cost = use_incremental_cost
def get_use_incremental_cost(self):
return self.use_incremental_cost
def get_macro_adjacency(self) -> list: def get_macro_adjacency(self) -> list:
""" """
Compute Adjacency Matrix Compute Adjacency Matrix
""" """
# NOTE: in pb.txt, netlist input count exceed certain threshold will be ommitted # NOTE: in pb.txt, netlist input count exceed certain threshold will be ommitted
#[MACRO][macro] #[MACRO][macro]
if self.FLAG_UPDATE_MACRO_ADJ:
# do some update
self.FLAG_UPDATE_MACRO_ADJ = False
module_indices = self.hard_macro_indices + self.soft_macro_indices module_indices = self.hard_macro_indices + self.soft_macro_indices
macro_adj = [0] * (self.hard_macro_cnt + self.soft_macro_cnt) * (self.hard_macro_cnt + self.soft_macro_cnt) macro_adj = [0] * (self.hard_macro_cnt + self.soft_macros_cnt) * (self.hard_macro_cnt + self.soft_macros_cnt)
assert len(macro_adj) == (self.hard_macro_cnt + self.soft_macro_cnt) * (self.hard_macro_cnt + self.soft_macro_cnt) assert len(macro_adj) == (self.hard_macro_cnt + self.soft_macros_cnt) * (self.hard_macro_cnt + self.soft_macros_cnt)
for row_idx, module_idx in enumerate(sorted(module_indices)): for row_idx, module_idx in enumerate(sorted(module_indices)):
# row index # row index
...@@ -1230,41 +1494,17 @@ class PlacementCost(object): ...@@ -1230,41 +1494,17 @@ class PlacementCost(object):
if h_module_name in curr_module.get_connection(): if h_module_name in curr_module.get_connection():
entry += curr_module.get_connection()[h_module_name] entry += curr_module.get_connection()[h_module_name]
macro_adj[row_idx * (self.hard_macro_cnt + self.soft_macro_cnt) + col_idx] = entry macro_adj[row_idx * (self.hard_macro_cnt + self.soft_macros_cnt) + col_idx] = entry
macro_adj[col_idx * (self.hard_macro_cnt + self.soft_macro_cnt) + row_idx] = entry macro_adj[col_idx * (self.hard_macro_cnt + self.soft_macros_cnt) + row_idx] = entry
return macro_adj return macro_adj
def is_node_fixed(self):
pass
def restore_placement(self, init_plc_pth: str):
"""
Read and retrieve .plc file information
"""
self.init_plc = init_plc_pth
self.__read_plc()
def optimize_stdcells(self):
pass
def update_node_coords(self):
pass
def fix_node_coord(self):
pass
def update_port_sides(self):
pass
def snap_ports_to_edges(self):
pass
def get_macro_and_clustered_port_adjacency(self): def get_macro_and_clustered_port_adjacency(self):
""" """
Compute Adjacency Matrix (Unclustered PORTs) Compute Adjacency Matrix (Unclustered PORTs)
if module is a PORT, assign nearest cell location even if OOB if module is a PORT, assign it to nearest cell location even if OOB
""" """
#[MACRO][macro] #[MACRO][macro]
module_indices = self.hard_macro_indices + self.soft_macro_indices module_indices = self.hard_macro_indices + self.soft_macro_indices
...@@ -1359,9 +1599,24 @@ class PlacementCost(object): ...@@ -1359,9 +1599,24 @@ class PlacementCost(object):
return macro_adj, sorted(cell_location) return macro_adj, sorted(cell_location)
def get_node_location(self, node_idx): def is_node_fixed(self):
pass pass
def optimize_stdcells(self):
pass
def update_node_coords(self):
pass
def update_port_sides(self):
pass
def snap_ports_to_edges(self):
pass
def get_node_location(self, node_idx):
pass
def get_grid_cell_of_node(self, node_idx): def get_grid_cell_of_node(self, node_idx):
""" if grid_cell at grid crossing, break-tie to upper right """ if grid_cell at grid crossing, break-tie to upper right
""" """
...@@ -1377,11 +1632,9 @@ class PlacementCost(object): ...@@ -1377,11 +1632,9 @@ class PlacementCost(object):
"""In case plc is loaded with fixed macros """In case plc is loaded with fixed macros
""" """
pass pass
def fix_node_coord(self): def fix_node_coord(self, node_idx):
"""Find all ports and fix their coordinates. self.modules_w_pins[node_idx].set_fix_flag(True)
"""
pass
def unplace_all_nodes(self): def unplace_all_nodes(self):
pass pass
...@@ -1410,6 +1663,9 @@ class PlacementCost(object): ...@@ -1410,6 +1663,9 @@ class PlacementCost(object):
def get_blockages(self): def get_blockages(self):
pass 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): def get_ref_node_id(self, node_idx=-1):
"""ref_node_id is used for macro_pins. Refers to the macro it belongs to. """ref_node_id is used for macro_pins. Refers to the macro it belongs to.
""" """
...@@ -1473,7 +1729,6 @@ class PlacementCost(object): ...@@ -1473,7 +1729,6 @@ class PlacementCost(object):
plt.show() plt.show()
plt.close('all') plt.close('all')
# Board Entity Definition # Board Entity Definition
class Port: class Port:
def __init__(self, name, x = 0.0, y = 0.0, side = "BOTTOM"): def __init__(self, name, x = 0.0, y = 0.0, side = "BOTTOM"):
...@@ -1483,7 +1738,7 @@ class PlacementCost(object): ...@@ -1483,7 +1738,7 @@ class PlacementCost(object):
self.side = side # "BOTTOM", "TOP", "LEFT", "RIGHT" self.side = side # "BOTTOM", "TOP", "LEFT", "RIGHT"
self.sink = {} # standard cells, macro pins, ports driven by this cell self.sink = {} # standard cells, macro pins, ports driven by this cell
self.connection = {} # [module_name] => edge degree self.connection = {} # [module_name] => edge degree
self.ifFixed = True self.fix_flag = True
self.placement = 0 # needs to be updated self.placement = 0 # needs to be updated
def get_name(self): def get_name(self):
...@@ -1551,6 +1806,12 @@ class PlacementCost(object): ...@@ -1551,6 +1806,12 @@ class PlacementCost(object):
def get_type(self): def get_type(self):
return "PORT" return "PORT"
def set_fix_flag(self, fix_flag):
self.fix_flag = fix_flag
def get_fix_flag(self):
return self.fix_flag
class SoftMacro: class SoftMacro:
def __init__(self, name, width, height, x = 0.0, y = 0.0): def __init__(self, name, width, height, x = 0.0, y = 0.0):
...@@ -1560,7 +1821,8 @@ class PlacementCost(object): ...@@ -1560,7 +1821,8 @@ class PlacementCost(object):
self.x = float(x) self.x = float(x)
self.y = float(y) self.y = float(y)
self.connection = {} # [module_name] => edge degree self.connection = {} # [module_name] => edge degree
self.ifFixed = False self.orientation = None
self.fix_flag = False
self.ifPlaced = True self.ifPlaced = True
self.location = 0 # needs to be updated self.location = 0 # needs to be updated
...@@ -1602,6 +1864,12 @@ class PlacementCost(object): ...@@ -1602,6 +1864,12 @@ class PlacementCost(object):
def get_connection(self): def get_connection(self):
return self.connection return self.connection
def set_orientation(self, orientation):
self.orientation = orientation
def get_orientation(self):
return self.orientation
def get_area(self): def get_area(self):
return self.width * self.height return self.width * self.height
...@@ -1617,6 +1885,12 @@ class PlacementCost(object): ...@@ -1617,6 +1885,12 @@ class PlacementCost(object):
def get_location(self): def get_location(self):
return self.location return self.location
def set_fix_flag(self, fix_flag):
self.fix_flag = fix_flag
def get_fix_flag(self):
return self.fix_flag
class SoftMacroPin: class SoftMacroPin:
def __init__( self, name, def __init__( self, name,
...@@ -1694,7 +1968,7 @@ class PlacementCost(object): ...@@ -1694,7 +1968,7 @@ class PlacementCost(object):
self.y = float(y) self.y = float(y)
self.orientation = orientation self.orientation = orientation
self.connection = {} # [module_name] => edge degree self.connection = {} # [module_name] => edge degree
self.ifFixed = False self.fix_flag = False
self.ifPlaced = True self.ifPlaced = True
self.location = 0 # needs to be updated self.location = 0 # needs to be updated
...@@ -1736,6 +2010,9 @@ class PlacementCost(object): ...@@ -1736,6 +2010,9 @@ class PlacementCost(object):
def set_orientation(self, orientation): def set_orientation(self, orientation):
self.orientation = orientation self.orientation = orientation
def get_orientation(self):
return self.orientation
def get_type(self): def get_type(self):
return "MACRO" return "MACRO"
...@@ -1754,6 +2031,12 @@ class PlacementCost(object): ...@@ -1754,6 +2031,12 @@ class PlacementCost(object):
def get_location(self): def get_location(self):
return self.location return self.location
def set_fix_flag(self, fix_flag):
self.fix_flag = fix_flag
def get_fix_flag(self):
return self.fix_flag
class HardMacroPin: class HardMacroPin:
def __init__(self, name, def __init__(self, name,
...@@ -1843,13 +2126,13 @@ def main(): ...@@ -1843,13 +2126,13 @@ def main():
print(plc.get_block_name()) print(plc.get_block_name())
print("Area: ", plc.get_area()) print("Area: ", plc.get_area())
print("Wirelength: ", plc.get_wirelength()) print("Wirelength: ", plc.get_wirelength())
print("# HARD_MACROs : %d"%(plc.get_hard_macro_count())) print("# HARD_MACROs : %d"%(plc.get_hard_macros_count()))
print("# HARD_MACRO_PINs : %d"%(plc.get_hard_macro_pin_count())) print("# HARD_MACRO_PINs : %d"%(plc.get_hard_macro_pins_count()))
print("# MACROs : %d"%(plc.get_hard_macro_count() + plc.get_soft_macro_count())) print("# MACROs : %d"%(plc.get_hard_macros_count() + plc.get_soft_macros_count()))
print("# MACRO_PINs : %d"%(plc.get_hard_macro_pin_count() + plc.get_soft_macro_pin_count())) print("# MACRO_PINs : %d"%(plc.get_hard_macro_pins_count() + plc.get_soft_macro_pins_count()))
print("# PORTs : %d"%(plc.get_port_count())) print("# PORTs : %d"%(plc.get_ports_count()))
print("# SOFT_MACROs : %d"%(plc.get_soft_macro_count())) print("# SOFT_MACROs : %d"%(plc.get_soft_macros_count()))
print("# SOFT_MACRO_PINs : %d"%(plc.get_soft_macro_pin_count())) print("# SOFT_MACRO_PINs : %d"%(plc.get_soft_macro_pins_count()))
print("# STDCELLs : 0") print("# STDCELLs : 0")
if __name__ == '__main__': if __name__ == '__main__':
......
import numpy as np
import sys,os,traceback
import argparse
import math
from absl import flags from absl import flags
from absl.flags import argparse_flags
from absl import app from absl import app
from torch import feature_alpha_dropout
from Plc_client import plc_client_os as plc_client_os from Plc_client import plc_client_os as plc_client_os
from Plc_client import plc_client as plc_client try:
import numpy as np from Plc_client import plc_client as plc_client
import sys except ImportError:
import time print("[PLC CLIENT MISSING] Downloading Google's API for testing!")
import math os.system("curl 'https://raw.githubusercontent.com/google-research/circuit_training/main/circuit_training/environment/plc_client.py' > ./Plc_client/plc_client.py")
from Plc_client import plc_client as plc_client
np.set_printoptions(threshold=sys.maxsize) np.set_printoptions(threshold=sys.maxsize)
FLAGS = flags.FLAGS # FLAGS = flags.FLAGS
class CircuitDataBaseTest(): """plc_client_os_test docstrings
# NETLIST_PATH = "./Plc_client/test/sample_clustered_uniform_two_soft/netlist.pb.txt"
# NETLIST_PATH = "./Plc_client/test/ariane_hard2soft/netlist.pb.txt" Test Utility Class for Google's API plc_wrapper_main with plc_client.py and plc_client_os.py
# NETLIST_PATH = "./Plc_client/test/ariane_soft2hard/netlist.pb.txt"
# NETLIST_PATH = "./Plc_client/test/ariane_port2soft/netlist.pb.txt" Example:
# NETLIST_PATH = "./Plc_client/test/sample_clustered_nomacro/netlist.pb.txt" At ./MacroPlacement/CodeElement, run the following command:
# NETLIST_PATH = "./Plc_client/test/sample_clustered_macroxy/netlist.pb.txt"
# NETLIST_PATH = "./Plc_client/test/ariane/netlist.pb.txt" $ python3 -m Plc_client.plc_client_os_test --netlist ./Plc_client/test/ariane/netlist.pb.txt\
# NETLIST_PATH = "./Plc_client/test/ariane133/netlist.pb.txt" --plc ./Plc_client/test/ariane/initial.plc\
NETLIST_PATH = "./Plc_client/test/testcases/TC1_MP1_0_0_P2_0_1.pb.txt" --width 356.592\
# NETLIST_PATH = "./Plc_client/test/testcases/TC24_MP1_0_0_MP2_4_4.pb.txt" --height 356.640\
# NETLIST_PATH = "./Plc_client/test/0P1M1m/netlist.pb.txt" --col 35\
# NETLIST_PATH = "./Plc_client/test/0P2M0m/netlist.pb.txt" --row 33\
# NETLIST_PATH = "./Plc_client/test/0P3M0m/netlist.pb.txt" --rpmh 10\
# NETLIST_PATH = "./Plc_client/test/0P4M0m/netlist.pb.txt" --rpmv 10\
# NETLIST_PATH = "./Plc_client/test/testcases_xm/TC_MP1_4_1_MP2_2_2_MP3_3_4_MP4_0_0.pb.txt" --marh 5\
--marv 5\
# Google's Ariane --smooth 2
# CANVAS_WIDTH = 356.592
# CANVAS_HEIGHT = 356.640 $ python3 -m Plc_client.plc_client_os_test --netlist ./Plc_client/test/ariane133/netlist.pb.txt\
# GRID_COL = 35 --plc ./Plc_client/test/ariane133/initial.plc\
# GRID_ROW = 33 --width 1599\
--height 1600.06\
# Ariane133 --col 24\
CANVAS_WIDTH = 1599.99 --row 21\
CANVAS_HEIGHT = 1600.06 --rpmh 10\
GRID_COL = 24 --rpmv 10\
GRID_ROW = 21 --marh 5\
--marv 5\
# Sample clustered --smooth 2
# CANVAS_WIDTH = 400
# CANVAS_HEIGHT = 400 Todo:
# GRID_COL = 4 * Clean up code
# GRID_ROW = 4 * Extract argument from command line
* Report index for each mismatch array entry
# PMm
# CANVAS_WIDTH = 100 """
# CANVAS_HEIGHT = 100
# GRID_COL = 5 class PlacementCostTest():
# GRID_ROW = 5
""" Canvas Setting Reference Table
def __init__(self, NETLIST_PATH) -> None: ++ Google's Ariane ++
- CANVAS_WIDTH = 356.592
- CANVAS_HEIGHT = 356.640
- GRID_COL = 35
- GRID_ROW = 33
++ Ariane133 ++
- CANVAS_WIDTH = 1599.99
- CANVAS_HEIGHT = 1600.06
- GRID_COL = 24
- GRID_ROW = 21
++ Sample clustered ++
- CANVAS_WIDTH = 400
- CANVAS_HEIGHT = 400
- GRID_COL = 4
- GRID_ROW = 4
++ PMm ++
- CANVAS_WIDTH = 100
- CANVAS_HEIGHT = 100
- GRID_COL = 5
- GRID_ROW = 5
"""
def __init__(self, NETLIST_PATH, PLC_PATH=None,
width=0, height=0,
column=0, row=0, rpmv=10, rpmh=10,
marh=10, marv=10, smooth=1) -> None:
self.NETLIST_PATH = NETLIST_PATH self.NETLIST_PATH = NETLIST_PATH
self.PLC_PATH = PLC_PATH
self.CANVAS_WIDTH = width
self.CANVAS_HEIGHT = height
self.GRID_COL = column
self.GRID_ROW = row
# for congestion computation
self.RPMV = rpmv
self.RPMH = rpmh
self.MARH = marh
self.MARV = marv
self.SMOOTH = smooth
def test_proxy_congestion(self):
def test_metadata(self):
print("############################ TEST METADATA ############################")
# Google's Binary Executable
self.plc = plc_client.PlacementCost(self.NETLIST_PATH)
# Open-sourced Implementation
self.plc_os = plc_client_os.PlacementCost(netlist_file=self.NETLIST_PATH,
macro_macro_x_spacing = 50,
macro_macro_y_spacing = 50)
# NOTE: must set canvas before restoring placement, otherwise OOB error
self.plc.set_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT)
self.plc.set_placement_grid(self.GRID_COL, self.GRID_ROW)
self.plc_os.set_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT)
self.plc_os.set_placement_grid(self.GRID_COL, self.GRID_ROW)
if self.PLC_PATH:
print("[PLC FILE FOUND] Loading info from .plc file")
self.plc_os.set_canvas_boundary_check(False)
self.plc_os.restore_placement(self.PLC_PATH,
ifInital=True,
ifValidate=True,
ifReadComment=False)
self.plc.set_canvas_boundary_check(False)
self.plc.restore_placement(self.PLC_PATH)
else:
print("[PLC FILE MISSING] Using only netlist info")
try:
assert int(self.plc_os.get_area()) == int(self.plc.get_area())
self.plc.set_routes_per_micron(1.0, 2.0)
self.plc_os.set_routes_per_micron(1.0, 2.0)
assert self.plc.get_routes_per_micron() == self.plc_os.get_routes_per_micron()
self.plc.set_overlap_threshold(2.0)
self.plc_os.set_overlap_threshold(2.0)
assert self.plc.get_overlap_threshold() == self.plc_os.get_overlap_threshold()
self.plc.set_congestion_smooth_range(2.0)
self.plc_os.set_congestion_smooth_range(2.0)
assert self.plc.get_congestion_smooth_range() == self.plc_os.get_congestion_smooth_range()
self.plc.set_macro_routing_allocation(3.0, 4.0)
self.plc_os.set_macro_routing_allocation(3.0, 4.0)
assert self.plc.get_macro_routing_allocation() == self.plc_os.get_macro_routing_allocation()
except Exception as e:
_, _, tb = sys.exc_info()
traceback.print_tb(tb)
tb_info = traceback.extract_tb(tb)
_, line, _, text = tb_info[-1]
print('[METADATA ERROR] at line {} in statement {}'\
.format(line, text))
exit(1)
# test get_macro_adjacency
plc_macroadj = self.plc.get_macro_adjacency()
plc_macroadj = np.array(plc_macroadj).reshape(int(math.sqrt(len(plc_macroadj))),\
int(math.sqrt(len(plc_macroadj))))
plcos_macroadj = self.plc_os.get_macro_adjacency()
plcos_macroadj = np.array(plcos_macroadj).reshape(int(math.sqrt(len(plcos_macroadj))),\
int(math.sqrt(len(plcos_macroadj))))
try:
assert(np.sum(np.nonzero(plc_macroadj - plcos_macroadj)) == 0)
except Exception as e:
print("[MACRO ADJ ERROR] Mismatched found -- {}".format(str(e)))
exit(1)
# test get_macro_and_clustered_port_adjacency
plc_clusteradj, plc_cell = self.plc.get_macro_and_clustered_port_adjacency()
plc_clusteradj = np.array(plc_clusteradj).reshape(int(math.sqrt(len(plc_clusteradj))),\
int(math.sqrt(len(plc_clusteradj))))
plcos_clusteradj, plcos_cell = self.plc_os.get_macro_and_clustered_port_adjacency()
plcos_clusteradj = np.array(plcos_clusteradj).reshape(int(math.sqrt(len(plcos_clusteradj))),\
int(math.sqrt(len(plcos_clusteradj))))
try:
for plc_adj, plcos_adj in zip(plc_clusteradj, plcos_clusteradj):
assert(np.sum(np.nonzero(plc_adj - plcos_adj)) == 0)
except Exception as e:
print("[MACRO AND CLUSTERED PORT ADJ ERROR] Mismatched found -- {}".format(str(e)))
exit(1)
print(" +++++++++++++++++++++++++++")
print(" +++ TEST METADATA: PASS +++")
print(" +++++++++++++++++++++++++++")
def view_canvas(self):
print("############################ VIEW CANVAS ############################")
self.plc_os = plc_client_os.PlacementCost(netlist_file=self.NETLIST_PATH,
macro_macro_x_spacing = 50,
macro_macro_y_spacing = 50)
self.plc.set_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT)
self.plc.set_placement_grid(self.GRID_COL, self.GRID_ROW)
self.plc_os.set_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT)
self.plc_os.set_placement_grid(self.GRID_COL, self.GRID_ROW)
# show canvas
self.plc_os.display_canvas()
def test_proxy_cost(self):
print("############################ TEST PROXY COST ############################")
# Google's Binary Executable # Google's Binary Executable
self.plc = plc_client.PlacementCost(self.NETLIST_PATH) self.plc = plc_client.PlacementCost(self.NETLIST_PATH)
# Open-sourced Implementation
self.plc_os = plc_client_os.PlacementCost(netlist_file=self.NETLIST_PATH,
macro_macro_x_spacing = 50,
macro_macro_y_spacing = 50)
if self.PLC_PATH:
print("[PLC FILE FOUND] Loading info from .plc file")
self.plc_os.set_canvas_boundary_check(False)
self.plc_os.restore_placement(self.PLC_PATH,
ifInital=True,
ifValidate=True,
ifReadComment=False)
self.plc.set_canvas_boundary_check(False)
# self.plc.restore_placement(self.PLC_PATH)
else:
print("[PLC FILE MISSING] Using only netlist info")
self.plc.set_routes_per_micron(self.RPMH, self.RPMV)
self.plc_os.set_routes_per_micron(self.RPMH, self.RPMV)
self.plc.set_macro_routing_allocation(self.MARH, self.MARV)
self.plc_os.set_macro_routing_allocation(self.MARH, self.MARV)
self.plc.set_congestion_smooth_range(self.SMOOTH)
self.plc_os.set_congestion_smooth_range(self.SMOOTH)
self.plc.set_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT)
self.plc.set_placement_grid(self.GRID_COL, self.GRID_ROW)
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: [IGNORE] Setting blockage has no effect on proxy cost computation
if False:
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.set_use_incremental_cost(True))
print(self.plc_os.get_soft_macros_count())
# HPWL
try:
assert int(self.plc_os.get_wirelength()) == int(self.plc.get_wirelength())
assert abs(self.plc.get_cost() - self.plc_os.get_cost()) <= 1e-3
except Exception as e:
print("[WIRELENGTH ERROR] Discrepancies found when computing wirelength -- {}, {}".format(str(self.plc.get_cost()), self.plc_os.get_cost()))
exit(1)
# Density
try:
assert int(sum(self.plc_os.get_grid_cells_density())) == int(sum(self.plc.get_grid_cells_density()))
assert int(self.plc_os.get_density_cost()) == int(self.plc.get_density_cost())
except Exception as e:
print("[DENSITY ERROR] Discrepancies found when computing density -- {}, {}".format(str(self.plc.get_density_cost()), self.plc_os.get_density_cost()))
exit(1)
# Congestion
try:
assert abs(sum(self.plc_os.get_horizontal_routing_congestion()) - sum(self.plc.get_horizontal_routing_congestion())) < 1e-3
assert abs(sum(self.plc_os.get_vertical_routing_congestion()) - sum(self.plc.get_vertical_routing_congestion())) < 1e-3
assert abs(self.plc.get_congestion_cost() - self.plc_os.get_congestion_cost()) < 1e-3
except Exception as e:
print("[CONGESTION ERROR] Discrepancies found when computing congestion -- {}".format(str(e)))
exit(1)
print(" +++++++++++++++++++++++++++++")
print(" +++ TEST PROXY COST: PASS +++")
print(" +++++++++++++++++++++++++++++")
def test_miscellaneous(self):
# Google's Binary Executable
self.plc = plc_client.PlacementCost(self.NETLIST_PATH)
self.plc_os = plc_client_os.PlacementCost(netlist_file=self.NETLIST_PATH,
macro_macro_x_spacing = 50,
macro_macro_y_spacing = 50)
print("****************** miscellaneous ******************")
self.plc.set_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT)
self.plc.set_placement_grid(self.GRID_COL, self.GRID_ROW)
self.plc_os.set_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT)
self.plc_os.set_placement_grid(self.GRID_COL, self.GRID_ROW)
NODE_IDX = 0
print("get_macro_indices", self.plc.get_macro_indices(), self.plc_os.get_macro_indices())
print("get_node_name", self.plc.get_node_name(NODE_IDX))
print("get_node_location", self.plc.get_node_location(NODE_IDX))
print("get_grid_cell_of_node", self.plc.get_grid_cell_of_node(NODE_IDX))
print("get_node_location", self.plc.get_node_location(NODE_IDX))
print("get_macro_orientation", self.plc.get_macro_orientation(NODE_IDX))
print("is_node_placed", self.plc.is_node_placed(NODE_IDX))
print("get_source_filename", self.plc.get_source_filename())
print("get_blockages", self.plc.get_blockages())
print("get_ref_node_id", self.plc.get_ref_node_id(NODE_IDX), self.plc.get_ref_node_id(NODE_IDX))
print("get_node_mask\n", np.array(self.plc.get_node_mask(NODE_IDX)).reshape((4,4)))
print("can_place_node", self.plc.can_place_node(0, 1))
print("***************************************************")
def test_proxy_congestion(self):
# Google's API
self.plc = plc_client.PlacementCost(self.NETLIST_PATH)
self.plc_os = plc_client_os.PlacementCost(self.NETLIST_PATH) self.plc_os = plc_client_os.PlacementCost(self.NETLIST_PATH)
# set rpm # set rpm
self.plc.set_routes_per_micron(10, 10) self.plc.set_routes_per_micron(10, 10)
self.plc_os.set_routes_per_micron(10, 10) self.plc_os.set_routes_per_micron(10, 10)
# self.plc.set_macro_routing_allocation(5, 5) self.plc.set_macro_routing_allocation(10, 10)
# self.plc_os.set_macro_routing_allocation(5, 5) self.plc_os.set_macro_routing_allocation(10, 10)
self.plc.set_macro_routing_allocation(0, 0)
self.plc_os.set_macro_routing_allocation(0, 0)
self.plc.set_congestion_smooth_range(0.0) self.plc.set_congestion_smooth_range(0.0)
self.plc_os.set_congestion_smooth_range(0.0) self.plc_os.set_congestion_smooth_range(0.0)
...@@ -77,18 +322,14 @@ class CircuitDataBaseTest(): ...@@ -77,18 +322,14 @@ class CircuitDataBaseTest():
self.plc_os.set_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT) self.plc_os.set_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT)
self.plc_os.set_placement_grid(self.GRID_COL, self.GRID_ROW) self.plc_os.set_placement_grid(self.GRID_COL, self.GRID_ROW)
print("Name: ", self.plc.get_source_filename().rsplit("/", 1)[1])
# self.plc_os.display_canvas(amplify=True)
# start = time.time()
temp_gl_h = np.array(self.plc.get_horizontal_routing_congestion()) temp_gl_h = np.array(self.plc.get_horizontal_routing_congestion())
temp_os_h = np.array(self.plc_os.get_horizontal_routing_congestion()) temp_os_h = np.array(self.plc_os.get_horizontal_routing_congestion())
print(temp_gl_h.reshape(self.GRID_COL, self.GRID_ROW)) print(temp_gl_h.reshape(self.GRID_COL, self.GRID_ROW))
print(temp_os_h.reshape(self.GRID_COL, self.GRID_ROW)) print(temp_os_h.reshape(self.GRID_COL, self.GRID_ROW))
print("GL H Congestion: ", temp_gl_h) print("GL H Congestion: ", self.plc.get_horizontal_routing_congestion())
print("OS H Congestion: ", temp_os_h) print("OS H Congestion: ", self.plc_os.get_horizontal_routing_congestion())
temp_gl_v = np.array(self.plc.get_vertical_routing_congestion()) temp_gl_v = np.array(self.plc.get_vertical_routing_congestion())
temp_os_v = np.array(self.plc_os.get_vertical_routing_congestion()) temp_os_v = np.array(self.plc_os.get_vertical_routing_congestion())
...@@ -133,140 +374,44 @@ class CircuitDataBaseTest(): ...@@ -133,140 +374,44 @@ class CircuitDataBaseTest():
####################################################################### BY ENTRY ####################################################################### BY ENTRY
print("**************BY ENTRY DIFF") print("**************BY ENTRY DIFF")
print(temp_gl_h_mc[0][6], temp_os_h_mc[0][6]) print(temp_gl_h_mc[0][6], temp_os_h_mc[0][6])
# print(temp_gl_v_mc[1][6], temp_os_v_mc[1][6])
######################################################################
# end = time.time()
# print("time elapsed:", end - start)
# for idx in range(len(temp_gl_h)):
# print("gl, os:", temp_gl_h[idx], temp_os_h[idx], temp_gl_v[idx], temp_os_v[idx])
# print("congestion summation gl os", sum(temp_gl_h), sum(temp_os_h), sum(temp_gl_v), sum(temp_os_v))
def test_proxy_cost(self):
# Google's Binary Executable
self.plc = plc_client.PlacementCost(self.NETLIST_PATH)
# Open-sourced Implementation
self.plc_os = plc_client_os.PlacementCost(netlist_file=self.NETLIST_PATH,
macro_macro_x_spacing = 50,
macro_macro_y_spacing = 50)
self.plc.set_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT)
self.plc.set_placement_grid(self.GRID_COL, self.GRID_ROW)
self.plc_os.set_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT)
self.plc_os.set_placement_grid(self.GRID_COL, self.GRID_ROW)
# print(self.plc_os.display_canvas())
print(self.plc_os.get_wirelength(), self.plc.get_wirelength())
assert int(self.plc_os.get_wirelength()) == int(self.plc.get_wirelength())
print("os wl cost", self.plc_os.get_cost())
print("gl wl cost", self.plc.get_cost())
assert abs(self.plc.get_cost() - self.plc_os.get_cost()) <= 10e-3
print("gl density\n", np.array(self.plc.get_grid_cells_density()).reshape(self.GRID_COL, self.GRID_ROW))
print("os density\n", np.array(self.plc_os.get_grid_cells_density()).reshape(self.GRID_COL, self.GRID_ROW))
assert int(sum(self.plc_os.get_grid_cells_density())) == int(sum(self.plc.get_grid_cells_density()))
assert int(self.plc_os.get_density_cost()) == int(self.plc.get_density_cost())
print("os density cost", self.plc_os.get_density_cost())
print("gl density cost", self.plc.get_density_cost())
def test_metadata(self):
# Google's Binary Executable
self.plc = plc_client.PlacementCost(self.NETLIST_PATH)
# Open-sourced Implementation
self.plc_os = plc_client_os.PlacementCost(netlist_file=self.NETLIST_PATH,
macro_macro_x_spacing = 50,
macro_macro_y_spacing = 50)
self.plc.set_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT)
self.plc.set_placement_grid(self.GRID_COL, self.GRID_ROW)
self.plc_os.set_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT)
self.plc_os.set_placement_grid(self.GRID_COL, self.GRID_ROW)
self.plc_os.get_grid_cells_density()
assert int(self.plc_os.get_area()) == int(self.plc.get_area())
self.plc.set_routes_per_micron(1.0, 2.0)
self.plc_os.set_routes_per_micron(1.0, 2.0)
assert self.plc.get_routes_per_micron() == self.plc_os.get_routes_per_micron()
self.plc.set_overlap_threshold(2.0)
self.plc_os.set_overlap_threshold(2.0)
assert self.plc.get_overlap_threshold() == self.plc_os.get_overlap_threshold()
self.plc.set_congestion_smooth_range(2.0)
self.plc_os.set_congestion_smooth_range(2.0)
assert self.plc.get_congestion_smooth_range() == self.plc_os.get_congestion_smooth_range()
self.plc.set_macro_routing_allocation(3.0, 4.0)
self.plc_os.set_macro_routing_allocation(3.0, 4.0)
assert self.plc.get_macro_routing_allocation() == self.plc_os.get_macro_routing_allocation()
# test get_macro_adjacency
plc_macroadj = self.plc.get_macro_adjacency()
plc_macroadj = np.array(plc_macroadj).reshape(int(math.sqrt(len(plc_macroadj))),\
int(math.sqrt(len(plc_macroadj))))
plcos_macroadj = self.plc_os.get_macro_adjacency()
plcos_macroadj = np.array(plcos_macroadj).reshape(int(math.sqrt(len(plcos_macroadj))),\
int(math.sqrt(len(plcos_macroadj))))
assert(np.sum(np.nonzero(plc_macroadj - plcos_macroadj)) == 0)
# test get_macro_and_clustered_port_adjacency
plc_clusteradj, plc_cell = self.plc.get_macro_and_clustered_port_adjacency()
plc_clusteradj = np.array(plc_clusteradj).reshape(int(math.sqrt(len(plc_clusteradj))),\
int(math.sqrt(len(plc_clusteradj))))
plcos_clusteradj, plcos_cell = self.plc_os.get_macro_and_clustered_port_adjacency()
plcos_clusteradj = np.array(plcos_clusteradj).reshape(int(math.sqrt(len(plcos_clusteradj))),\
int(math.sqrt(len(plcos_clusteradj))))
assert(plc_cell == plcos_cell) def parse_flags(argv):
parser = argparse_flags.ArgumentParser(description='An argparse + app.run example')
for plc_adj, plcos_adj in zip(plc_clusteradj, plcos_clusteradj): parser.add_argument("--netlist", required=True,
assert(np.sum(np.nonzero(plc_adj - plcos_adj)) == 0) help="Path to netlist in pb.txt")
parser.add_argument("--plc", required=False,
def test_miscellaneous(self): help="Path to plc in .plc")
# Google's Binary Executable parser.add_argument("--width", type=float, required=True,
self.plc = plc_client.PlacementCost(self.NETLIST_PATH) help="Canvas width")
self.plc_os = plc_client_os.PlacementCost(netlist_file=self.NETLIST_PATH, parser.add_argument("--height", type=float, required=True,
macro_macro_x_spacing = 50, help="Canvas height")
macro_macro_y_spacing = 50) parser.add_argument("--col", type=int, required=True,
print("****************** miscellaneous ******************") help="Grid column")
self.plc.set_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT) parser.add_argument("--row", type=int, required=True,
self.plc.set_placement_grid(self.GRID_COL, self.GRID_ROW) help="Grid row")
self.plc_os.set_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT) parser.add_argument("--rpmh", type=float, default=10, required=False,
self.plc_os.set_placement_grid(self.GRID_COL, self.GRID_ROW) help="Grid row")
NODE_IDX = 0 parser.add_argument("--rpmv", type=float, default=10, required=False,
print("get_macro_indices", self.plc.get_macro_indices(), self.plc_os.get_macro_indices()) help="Grid row")
print("get_node_name", self.plc.get_node_name(NODE_IDX)) parser.add_argument("--marh", type=float, default=10, required=False,
print("get_node_location", self.plc.get_node_location(NODE_IDX)) help="Grid row")
print("get_grid_cell_of_node", self.plc.get_grid_cell_of_node(NODE_IDX)) parser.add_argument("--marv", type=float, default=10, required=False,
print("get_node_location", self.plc.get_node_location(NODE_IDX)) help="Grid row")
print("get_macro_orientation", self.plc.get_macro_orientation(NODE_IDX)) parser.add_argument("--smooth", type=float, default=1, required=False,
print("is_node_placed", self.plc.is_node_placed(NODE_IDX)) help="Grid row")
print("get_source_filename", self.plc.get_source_filename()) return parser.parse_args(argv[1:])
print("get_blockages", self.plc.get_blockages())
print("get_ref_node_id", self.plc.get_ref_node_id(NODE_IDX), self.plc.get_ref_node_id(NODE_IDX)) def main(args):
print("get_node_mask\n", np.array(self.plc.get_node_mask(NODE_IDX)).reshape((4,4))) if args.plc:
print("can_place_node", self.plc.can_place_node(0, 1)) PCT = PlacementCostTest(args.netlist, args.plc, args.width, args.height,
print("***************************************************") args.col, args.row, args.rpmv, args.rpmv,
args.marh, args.marv, args.smooth)
else:
def main(argv): PCT = PlacementCostTest(args.netlist, args.width, args.height,
args = sys.argv[1:] args.col, args.row, args.rpmv, args.rpmv,
temp = CircuitDataBaseTest(args[0]) args.marh, args.marv, args.smooth)
temp.test_proxy_congestion() PCT.test_metadata()
# temp.test_proxy_cost() PCT.test_proxy_cost()
# temp.test_metadata()
# temp.test_miscellaneous() if __name__ == '__main__':
app.run(main, flags_parser=parse_flags)
if __name__ == "__main__": \ No newline at end of file
app.run(main)
\ No newline at end of file
...@@ -201,7 +201,8 @@ while allowing soft macros (standard-cell clusters) to also find good locations. ...@@ -201,7 +201,8 @@ while allowing soft macros (standard-cell clusters) to also find good locations.
- [Hypergraph clustering](./CodeElements/Clustering/) clusters millions of standard cells into a few thousand clusters. In Circuit Training, the purpose of clustering is to enable an approximate but fast standard cell placement that facilitates policy network optimization. - [Hypergraph clustering](./CodeElements/Clustering/) clusters millions of standard cells into a few thousand clusters. In Circuit Training, the purpose of clustering is to enable an approximate but fast standard cell placement that facilitates policy network optimization.
- [Force-directed placement](./CodeElements/FDPlacement/) places the center of each standard cell cluster onto centers of gridcells generated by [Gridding](./CodeElements/Gridding/). - [Force-directed placement](./CodeElements/FDPlacement/) places the center of each standard cell cluster onto centers of gridcells generated by [Gridding](./CodeElements/Gridding/).
- [Simulated annealing](./CodeElements/SimulatedAnnealing/) places the center of each macro onto centers of gridcells generated by [Gridding](./CodeElements/Gridding/). In Circuit Training, simulated annealing is used as a baseline to show the relative sample efficiency of RL. - [Simulated annealing](./CodeElements/SimulatedAnnealing/) places the center of each macro onto centers of gridcells generated by [Gridding](./CodeElements/Gridding/). In Circuit Training, simulated annealing is used as a baseline to show the relative sample efficiency of RL.
- [LEF/DEF and Bookshelf (OpenDB, RosettaStone) translators](./CodeElements/FormatTranslators/) ease the translation between different representations of the same netlist. - [LEF/DEF and Bookshelf (OpenDB, RosettaStone) translators](./CodeElements/FormatTranslators/) ease the translation between different representations of the same netlist.
- [Plc client](./CodeElements/Plc_client/) implements all three components of the proxy cost function: wirelength cost, density cost and congestion cost.
<!--## **Reproducible Example Solutions** --> <!--## **Reproducible Example Solutions** -->
...@@ -218,18 +219,16 @@ We provide a competitive baseline for [Google Brain's Circuit Training](https:// ...@@ -218,18 +219,16 @@ We provide a competitive baseline for [Google Brain's Circuit Training](https://
- We do understand that Google has been working hard to complete the open-sourcing of Morpheus, and that this effort continues today. However, as pointed out in [this Doc](https://docs.google.com/document/d/1vkPRgJEiLIyT22AkQNAxO8JtIKiL95diVdJ_O4AFtJ8/edit?usp=sharing), it has been more than a year since "Data and Code Availability" was committed with publication of the [Nature paper](https://www.nature.com/articles/s41586-021-03544-w). We consider our work a "backstop" or "safety net" for Google's internal efforts, and a platform for researchers to build on. - We do understand that Google has been working hard to complete the open-sourcing of Morpheus, and that this effort continues today. However, as pointed out in [this Doc](https://docs.google.com/document/d/1vkPRgJEiLIyT22AkQNAxO8JtIKiL95diVdJ_O4AFtJ8/edit?usp=sharing), it has been more than a year since "Data and Code Availability" was committed with publication of the [Nature paper](https://www.nature.com/articles/s41586-021-03544-w). We consider our work a "backstop" or "safety net" for Google's internal efforts, and a platform for researchers to build on.
**What can others contribute?** **What can others contribute?**
- Our shopping list includes the following. Please join in! - Our shopping list (updated August 2022) includes the following. Please join in!
- force-directed placement (and API): documentation and implementation
- adjacency matrix generation: documentation and implementation
- simulated annealing on the gridded canvas: documentation and implementation - simulated annealing on the gridded canvas: documentation and implementation
- force-directed placement: documentation and implementation
- donated cloud resources (credits) for experimental studies - donated cloud resources (credits) for experimental studies
- relevant testcases with reference implementations and implementation flows (Cadence, OpenROAD preferred since scripts can be shared) - relevant testcases with reference implementations and implementation flows (Cadence, OpenROAD preferred since scripts can be shared)
- protobuf, lef/def, Bookshelf: detailed and confirmed documentation, plus tests and other help to improve our initial versions of translators - improved "fakeram" generator for the ASAP7 research PDK
- "fakeram" generator for the ASAP7 research PDK
- qrctechfile for NanGate45
**What is your timeline?** **What is your timeline?**
- We hope to show significant progress at the [DAC-2022 Birds-of-a-Feather](https://59dac.conference-program.com/session/?sess=sess294) meeting (Open-Source EDA and Benchmarking Summit) on July 12, 2022, 7-10pm in Room 3000 of Moscone West in San Francisco. - We showed our [progress](https://open-source-eda-birds-of-a-feather.github.io/doc/slides/MacroPlacement-SpecPart-DAC-BOF-v5.pdf) at the Open-Source EDA and Benchmarking Summit birds-of-a-feather [meeting](https://open-source-eda-birds-of-a-feather.github.io/) on July 12 at DAC-2022.
- We are now (late August 2022) studying benefits and limitations of the CT methodology itself, as noted in [this Doc](https://docs.google.com/document/d/1c-uweo3DHiCWZyBzAdNCqqcOrAbKq1sVIfY0_4bFCYE/edit).
## **Related Links** ## **Related Links**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment