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-*
Flows/job
Flows/util/__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 @@
### Input: a list of macros (each macro has a width and a height)
### Output: best choice of n_rows and n_cols
import os
from math import floor
from math import ceil
......@@ -22,6 +21,7 @@ class Grid:
self.y_ = y
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.macro_area = 0.0
# Check if there is an overlap with other placed macros
def CheckOverlap(lx, ly, ux, uy, macro_box):
......@@ -33,19 +33,30 @@ def CheckOverlap(lx, ly, ux, uy, macro_box):
pass
else:
return True # there is an overlap
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
# n = num_cols
def PlaceMacros(macro_map, grid_list, chip_width, chip_height, n):
### All the macro must be placed on the center of one grid
#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 = []
# Place macro one by one
for key, value in macro_map.items():
width = value[0]
......@@ -55,7 +66,6 @@ def PlaceMacros(macro_map, grid_list, chip_width, chip_height, n):
for grid in grid_list:
if (grid.placed_ == True):
continue # this grid has been occupied
# if the macro is placed on this
x = grid.x_
y = grid.y_
......@@ -67,12 +77,10 @@ def PlaceMacros(macro_map, grid_list, chip_width, chip_height, n):
# check if the macro is within the outline
if (ux > chip_width or uy > chip_height):
continue
# check if there is an overlap with other macros
if (CheckOverlap(lx, ly, ux, uy, macro_bbox) == True):
continue
# place current macro on this grid
grid.placed_ = True
placed_flag = True
......@@ -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):
grid_id = i * n + j # n is the num_cols
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
# cannot find a valid position for the macro
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
def Gridding(macro_width_list, macro_height_list,
chip_width, chip_height, tolerance = 0.1,
min_n_rows = 10, min_n_cols = 10,
max_n_rows = 100, max_n_cols = 100,
max_rows_times_cols = 3000,
min_rows_times_cols = 500,
max_aspect_ratio = 1.5):
chip_width, chip_height,
min_n_rows = 10, min_n_cols = 10,
max_n_rows = 128, max_n_cols = 128,
min_num_grid_cells = 500,
max_num_grid_cells = 2500,
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
if (len(macro_width_list) != len(macro_height_list)):
print("[Error] The macro information is wrong!!!")
......@@ -116,30 +145,34 @@ def Gridding(macro_width_list, macro_height_list,
macro_map = { }
for i in range(len(macro_width_list)):
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_bbox = [] # (lx, ly, ux, uy) for each bounding box
### Print information
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("[INFO] Sorted Macro Information")
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")
### 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
m_best = -1
n_best = -1
best_cost = 2.0 # cost should be less than 2.0 based on definition
choice_map = { }
for m in range(min_n_rows, max_n_rows + 1):
best_metric = -1.0
choice_map = { } # [m][n] : (ver_cost, hor_cost, empty_ratio)
for m in range(min_n_rows, max_n_rows):
choice_map[m] = { }
for n in range(min_n_cols, max_n_cols + 1):
if (m * n > max_rows_times_cols):
for n in range(min_n_cols, max_n_cols):
if (m * n > max_num_grid_cells):
break
if (m * n < min_rows_times_cols):
if (m * n < min_num_grid_cells):
continue
### Step1: Divide the canvas into grids
......@@ -148,10 +181,10 @@ def Gridding(macro_width_list, macro_height_list,
grid_width = chip_width / n
if (grid_height / grid_width > max_aspect_ratio):
continue
if (grid_width / grid_height > max_aspect_ratio):
continue
### Step2: Try to place macros on canvas
grid_list = []
for i in range(m):
for j in range(n):
......@@ -160,24 +193,24 @@ def Gridding(macro_width_list, macro_height_list,
grid_id = len(grid_list)
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
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
else:
### Calculate the cost
total_grid_width = 0.0
total_grid_height = 0.0
### compute the empty ratio
used_threshold = 1e-5
num_empty_grids = 0
for grid in grid_list:
if (len(grid.macros_id_) > 0):
total_grid_width += grid.width_
total_grid_height += grid.height_
# calculate h_cost
cost = 1.0 - sum(macro_width_list) / total_grid_width
cost += 1.0 - sum(macro_height_list) / total_grid_height
choice_map[m][n] = cost
if (cost < best_cost):
best_cost = cost
if (grid.macro_area / (grid_width * grid_height) < used_threshold):
num_empty_grids += 1
metric = 1.0 - value[0] / value[1]
metric += 1.0 - value[2] / value[3]
metric += num_empty_grids / len(grid_list)
choice_map[m][n] = metric
if (metric > best_metric):
best_metric = metric
m_best = m
n_best = n
m_opt = m_best
......@@ -188,9 +221,9 @@ def Gridding(macro_width_list, macro_height_list,
print("n_best = ", n_best)
print("tolerance = ", tolerance)
for [m, m_map] in choice_map.items():
for [n, cost] in m_map.items():
print("m = ", m , " n = ", n, " cost = ", cost)
if ((cost <= (1.0 + tolerance) * best_cost) and (m * n < num_grids_opt)):
for [n, metric] in m_map.items():
print("m = ", m , " n = ", n, " metric = ", metric)
if ((metric >= (1.0 - tolerance) * best_metric) and (m * n < num_grids_opt)):
m_opt = m
n_opt = n
num_grids_opt = m * n
......@@ -201,7 +234,7 @@ def Gridding(macro_width_list, macro_height_list,
class GriddingLefDefInterface:
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_aspect_ratio = 1.5):
self.src_dir = src_dir
......@@ -227,11 +260,11 @@ class GriddingLefDefInterface:
self.GenerateHypergraph()
self.ExtractInputs()
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.max_n_rows, self.max_n_cols,
self.max_rows_times_cols, self.min_rows_times_cols,
self.max_aspect_ratio)
self.min_rows_times_cols, self.max_rows_times_cols,
self.max_aspect_ratio, self.tolerance)
def GetNumRows(self):
return self.m_opt
......@@ -278,6 +311,7 @@ class GriddingLefDefInterface:
f.close()
items = content[0].split()
print(items)
self.chip_width = float(items[2]) - float(items[0])
self.chip_height = float(items[3]) - float(items[1])
......
......@@ -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 \
-o /usr/local/bin/plc_wrapper_main
# 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
......@@ -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)]
$$
## Density Cost Computation
## Density Cost Computation
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.
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.
......
# 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
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
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')
......@@ -25,6 +43,13 @@ class PlacementCost(object):
self.macro_macro_x_spacing = macro_macro_x_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
assert os.path.isfile(self.netlist_file)
......@@ -38,9 +63,9 @@ class PlacementCost(object):
self.overlap_thres = 0.0
self.hrouting_alloc = 0.0
self.vrouting_alloc = 0.0
self.macro_horizontal_routing_allocation = 0.0
self.macro_vertical_routing_allocation = 0.0
self.canvas_boundary_check = True
# net information
self.net_cnt = 0
......@@ -60,6 +85,11 @@ class PlacementCost(object):
# macro to pins look-up table: [MACRO_NAME] => [PIN_NAME]
self.hard_macros_to_inpins = {}
self.soft_macros_to_inpins = {}
# unknown
self.use_incremental_cost = False
# blockage
self.blockages = []
# read netlist
self.__read_protobuf()
......@@ -78,16 +108,16 @@ class PlacementCost(object):
# initial grid mask
self.global_node_mask = [0] * self.grid_col * self.grid_row
# 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_pin_cnt = len(self.hard_macro_pin_indices)
self.soft_macro_cnt = len(self.soft_macro_indices)
self.soft_macro_pin_cnt = len(self.soft_macro_pin_indices)
self.module_cnt = self.hard_macro_cnt + self.soft_macro_cnt + self.port_cnt
self.hard_macro_pins_cnt = len(self.hard_macro_pin_indices)
self.soft_macros_cnt = len(self.soft_macro_indices)
self.soft_macro_pins_cnt = len(self.soft_macro_pin_indices)
self.module_cnt = self.hard_macro_cnt + self.soft_macros_cnt + self.ports_cnt
# assert module and pin count are correct
assert (len(self.modules)) == self.module_cnt
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
def __peek(self, f:io.TextIOWrapper):
......@@ -185,12 +215,12 @@ class PlacementCost(object):
try:
assert 'x' in attr_dict.keys()
except AssertionError:
logging.warning('x is not defined')
logging.warning('[NETLIST PARSER ERROR] x is not defined')
try:
assert 'y' in attr_dict.keys()
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],
height = attr_dict['height'][1],
......@@ -317,30 +347,213 @@ class PlacementCost(object):
# mapping connection degree to each macros
self.__update_connection()
def __read_plc(self):
def __read_plc(self, plc_pth: str):
"""
Plc file Parser
"""
with open(self.init_plc) as fp:
line = fp.readline()
while line:
# skip comments
if re.search(r"\S", line)[0] == '#':
# IMPORTANT: Advance pt
line = fp.readline()
continue
# words itemize into list
line_item = re.findall(r'[0-9A-Za-z\.\-]+', line)
# skip empty lines
if len(line_item) == 0:
# IMPORTANT: Advance pt
line = fp.readline()
continue
line = fp.readline()
# meta information
_columns = 0
_rows = 0
_width = 0.0
_height = 0.0
_area = 0.0
_block = None
_routes_per_micron_hor = 0.0
_routes_per_micron_ver = 0.0
_routes_used_by_macros_hor = 0.0
_routes_used_by_macros_ver = 0.0
_smoothing_factor = 0
_overlap_threshold = 0.0
# node information
_hard_macros_cnt = 0
_hard_macro_pins_cnt = 0
_macros_cnt = 0
_macro_pin_cnt = 0
_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):
"""
......@@ -371,17 +584,15 @@ class PlacementCost(object):
if macro_type == "MACRO" or macro_type == "macro":
weight = pin.get_weight()
macro.add_connections(inputs[k], weight)
def __update_placement(self):
# assign modules to grid cells
pass
def get_cost(self) -> float:
"""
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:
"""
......@@ -393,20 +604,20 @@ class PlacementCost(object):
total_area += mod.get_area()
return total_area
def get_hard_macro_count(self) -> int:
def get_hard_macros_count(self) -> int:
return self.hard_macro_cnt
def get_port_count(self) -> int:
return self.port_cnt
def get_ports_count(self) -> int:
return self.ports_cnt
def get_soft_macro_count(self) -> int:
return self.soft_macro_cnt
def get_soft_macros_count(self) -> int:
return self.soft_macros_cnt
def get_hard_macro_pin_count(self) -> int:
return self.hard_macro_pin_cnt
def get_hard_macro_pins_count(self) -> int:
return self.hard_macro_pins_cnt
def get_soft_macro_pin_count(self) -> int:
return self.soft_macro_pin_cnt
def get_soft_macro_pins_count(self) -> int:
return self.soft_macro_pins_cnt
def get_wirelength(self) -> float:
"""
......@@ -521,33 +732,15 @@ class PlacementCost(object):
def get_congestion_cost(self):
#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)
# 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):
"""
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)
col = math.floor(x_pos / self.grid_width)
return row, col
......@@ -661,6 +854,10 @@ class PlacementCost(object):
"""
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)
density_cost = 0.0
......@@ -688,6 +885,11 @@ class PlacementCost(object):
self.width = width
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_height = float(self.height/self.grid_row)
return True
......@@ -702,9 +904,15 @@ class PlacementCost(object):
"""
Set grid col/row
"""
print("#[PLACEMENT GRID] Col: %d, Row: %d" % (grid_col, grid_row))
self.grid_col = grid_col
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.H_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):
"""
return sorted(self.hard_macro_indices + self.soft_macro_indices)
def set_project_name(self, project_name):
"""
Set Project name
"""
self.project_name = project_name
def get_project_name(self) -> str:
"""
Return Project name
"""
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:
"""
......@@ -742,6 +962,10 @@ class PlacementCost(object):
"""
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.vroutes_per_micron = vroutes_per_micron
......@@ -755,7 +979,11 @@ class PlacementCost(object):
"""
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:
"""
......@@ -767,6 +995,7 @@ class PlacementCost(object):
"""
Set Overlap Threshold
"""
print("#[OVERLAP THRESHOLD] Threshold: %.4f" % (overlap_thres))
self.overlap_thres = overlap_thres
def get_overlap_threshold(self) -> float:
......@@ -775,13 +1004,25 @@ class PlacementCost(object):
"""
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:
return False
"""
return canvas_boundary_check
"""
return self.canvas_boundary_check
def set_macro_routing_allocation(self, hrouting_alloc:float, vrouting_alloc:float) -> None:
"""
Set Vertical/Horizontal Macro Allocation
"""
# Flag updates
self.FLAG_UPDATE_CONGESTION = True
self.hrouting_alloc = hrouting_alloc
self.vrouting_alloc = vrouting_alloc
......@@ -915,7 +1156,6 @@ class PlacementCost(object):
self.t_routing(temp_gcell, weight)
return
def __macro_route_over_grid_cell(self, mod_x, mod_y, mod_w, mod_h):
"""
private function for add module to grid cells
......@@ -1019,29 +1259,38 @@ class PlacementCost(object):
def get_vertical_routing_congestion(self):
# TODO: detect if we need to run
self.get_routing()
if self.FLAG_UPDATE_CONGESTION:
self.get_routing()
return self.V_routing_cong
def get_horizontal_routing_congestion(self):
# TODO: detect if we need to run
self.get_routing()
if self.FLAG_UPDATE_CONGESTION:
self.get_routing()
return self.H_routing_cong
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_h_routes = self.grid_height * self.hroutes_per_micron
self.grid_v_routes = self.grid_width * self.vroutes_per_micron
self.grid_h_routes = self.grid_height * self.hroutes_per_micron
# reset grid
self.H_routing_cong = [0] * self.grid_row * self.grid_col
self.V_routing_cong = [0] * self.grid_row * self.grid_col
# reset grid
self.H_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.V_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.FLAG_UPDATE_CONGESTION = False
net_count = 0
for mod in self.modules_w_pins:
norm_fact = 1.0
curr_type = mod.get_type()
......@@ -1117,8 +1366,7 @@ class PlacementCost(object):
# 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.H_routing_cong = [sum(x) for x in zip(self.H_routing_cong, self.H_macro_routing_cong)]
def __smooth_routing_cong(self):
temp_V_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):
"""
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):
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:
"""
Compute Adjacency Matrix
"""
# NOTE: in pb.txt, netlist input count exceed certain threshold will be ommitted
#[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
macro_adj = [0] * (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_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_macros_cnt) * (self.hard_macro_cnt + self.soft_macros_cnt)
for row_idx, module_idx in enumerate(sorted(module_indices)):
# row index
......@@ -1230,41 +1494,17 @@ class PlacementCost(object):
if h_module_name in curr_module.get_connection():
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[col_idx * (self.hard_macro_cnt + self.soft_macro_cnt) + row_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_macros_cnt) + row_idx] = entry
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):
"""
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]
module_indices = self.hard_macro_indices + self.soft_macro_indices
......@@ -1359,9 +1599,24 @@ class PlacementCost(object):
return macro_adj, sorted(cell_location)
def get_node_location(self, node_idx):
def is_node_fixed(self):
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):
""" if grid_cell at grid crossing, break-tie to upper right
"""
......@@ -1377,11 +1632,9 @@ class PlacementCost(object):
"""In case plc is loaded with fixed macros
"""
pass
def fix_node_coord(self):
"""Find all ports and fix their coordinates.
"""
pass
def fix_node_coord(self, node_idx):
self.modules_w_pins[node_idx].set_fix_flag(True)
def unplace_all_nodes(self):
pass
......@@ -1410,6 +1663,9 @@ class PlacementCost(object):
def get_blockages(self):
pass
def create_blockage(self, minx, miny, maxx, maxy, blockage_rate):
self.blockages.append([minx, miny, maxx, maxy, blockage_rate])
def get_ref_node_id(self, node_idx=-1):
"""ref_node_id is used for macro_pins. Refers to the macro it belongs to.
"""
......@@ -1473,7 +1729,6 @@ class PlacementCost(object):
plt.show()
plt.close('all')
# Board Entity Definition
class Port:
def __init__(self, name, x = 0.0, y = 0.0, side = "BOTTOM"):
......@@ -1483,7 +1738,7 @@ class PlacementCost(object):
self.side = side # "BOTTOM", "TOP", "LEFT", "RIGHT"
self.sink = {} # standard cells, macro pins, ports driven by this cell
self.connection = {} # [module_name] => edge degree
self.ifFixed = True
self.fix_flag = True
self.placement = 0 # needs to be updated
def get_name(self):
......@@ -1551,6 +1806,12 @@ class PlacementCost(object):
def get_type(self):
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:
def __init__(self, name, width, height, x = 0.0, y = 0.0):
......@@ -1560,7 +1821,8 @@ class PlacementCost(object):
self.x = float(x)
self.y = float(y)
self.connection = {} # [module_name] => edge degree
self.ifFixed = False
self.orientation = None
self.fix_flag = False
self.ifPlaced = True
self.location = 0 # needs to be updated
......@@ -1602,6 +1864,12 @@ class PlacementCost(object):
def get_connection(self):
return self.connection
def set_orientation(self, orientation):
self.orientation = orientation
def get_orientation(self):
return self.orientation
def get_area(self):
return self.width * self.height
......@@ -1617,6 +1885,12 @@ class PlacementCost(object):
def get_location(self):
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:
def __init__( self, name,
......@@ -1694,7 +1968,7 @@ class PlacementCost(object):
self.y = float(y)
self.orientation = orientation
self.connection = {} # [module_name] => edge degree
self.ifFixed = False
self.fix_flag = False
self.ifPlaced = True
self.location = 0 # needs to be updated
......@@ -1736,6 +2010,9 @@ class PlacementCost(object):
def set_orientation(self, orientation):
self.orientation = orientation
def get_orientation(self):
return self.orientation
def get_type(self):
return "MACRO"
......@@ -1754,6 +2031,12 @@ class PlacementCost(object):
def get_location(self):
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:
def __init__(self, name,
......@@ -1843,13 +2126,13 @@ def main():
print(plc.get_block_name())
print("Area: ", plc.get_area())
print("Wirelength: ", plc.get_wirelength())
print("# HARD_MACROs : %d"%(plc.get_hard_macro_count()))
print("# HARD_MACRO_PINs : %d"%(plc.get_hard_macro_pin_count()))
print("# MACROs : %d"%(plc.get_hard_macro_count() + plc.get_soft_macro_count()))
print("# MACRO_PINs : %d"%(plc.get_hard_macro_pin_count() + plc.get_soft_macro_pin_count()))
print("# PORTs : %d"%(plc.get_port_count()))
print("# SOFT_MACROs : %d"%(plc.get_soft_macro_count()))
print("# SOFT_MACRO_PINs : %d"%(plc.get_soft_macro_pin_count()))
print("# HARD_MACROs : %d"%(plc.get_hard_macros_count()))
print("# HARD_MACRO_PINs : %d"%(plc.get_hard_macro_pins_count()))
print("# MACROs : %d"%(plc.get_hard_macros_count() + plc.get_soft_macros_count()))
print("# MACRO_PINs : %d"%(plc.get_hard_macro_pins_count() + plc.get_soft_macro_pins_count()))
print("# PORTs : %d"%(plc.get_ports_count()))
print("# SOFT_MACROs : %d"%(plc.get_soft_macros_count()))
print("# SOFT_MACRO_PINs : %d"%(plc.get_soft_macro_pins_count()))
print("# STDCELLs : 0")
if __name__ == '__main__':
......
import numpy as np
import sys,os,traceback
import argparse
import math
from absl import flags
from absl.flags import argparse_flags
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 as plc_client
import numpy as np
import sys
import time
import math
try:
from Plc_client import plc_client as plc_client
except ImportError:
print("[PLC CLIENT MISSING] Downloading Google's API for testing!")
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)
FLAGS = flags.FLAGS
class CircuitDataBaseTest():
# NETLIST_PATH = "./Plc_client/test/sample_clustered_uniform_two_soft/netlist.pb.txt"
# NETLIST_PATH = "./Plc_client/test/ariane_hard2soft/netlist.pb.txt"
# NETLIST_PATH = "./Plc_client/test/ariane_soft2hard/netlist.pb.txt"
# NETLIST_PATH = "./Plc_client/test/ariane_port2soft/netlist.pb.txt"
# NETLIST_PATH = "./Plc_client/test/sample_clustered_nomacro/netlist.pb.txt"
# NETLIST_PATH = "./Plc_client/test/sample_clustered_macroxy/netlist.pb.txt"
# NETLIST_PATH = "./Plc_client/test/ariane/netlist.pb.txt"
# NETLIST_PATH = "./Plc_client/test/ariane133/netlist.pb.txt"
NETLIST_PATH = "./Plc_client/test/testcases/TC1_MP1_0_0_P2_0_1.pb.txt"
# NETLIST_PATH = "./Plc_client/test/testcases/TC24_MP1_0_0_MP2_4_4.pb.txt"
# NETLIST_PATH = "./Plc_client/test/0P1M1m/netlist.pb.txt"
# NETLIST_PATH = "./Plc_client/test/0P2M0m/netlist.pb.txt"
# NETLIST_PATH = "./Plc_client/test/0P3M0m/netlist.pb.txt"
# NETLIST_PATH = "./Plc_client/test/0P4M0m/netlist.pb.txt"
# NETLIST_PATH = "./Plc_client/test/testcases_xm/TC_MP1_4_1_MP2_2_2_MP3_3_4_MP4_0_0.pb.txt"
# 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) -> None:
# FLAGS = flags.FLAGS
"""plc_client_os_test docstrings
Test Utility Class for Google's API plc_wrapper_main with plc_client.py and plc_client_os.py
Example:
At ./MacroPlacement/CodeElement, run the following command:
$ python3 -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
$ python3 -m Plc_client.plc_client_os_test --netlist ./Plc_client/test/ariane133/netlist.pb.txt\
--plc ./Plc_client/test/ariane133/initial.plc\
--width 1599\
--height 1600.06\
--col 24\
--row 21\
--rpmh 10\
--rpmv 10\
--marh 5\
--marv 5\
--smooth 2
Todo:
* Clean up code
* Extract argument from command line
* Report index for each mismatch array entry
"""
class PlacementCostTest():
""" Canvas Setting Reference Table
++ 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.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
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)
# set rpm
self.plc.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_os.set_macro_routing_allocation(5, 5)
self.plc.set_macro_routing_allocation(0, 0)
self.plc_os.set_macro_routing_allocation(0, 0)
self.plc.set_macro_routing_allocation(10, 10)
self.plc_os.set_macro_routing_allocation(10, 10)
self.plc.set_congestion_smooth_range(0.0)
self.plc_os.set_congestion_smooth_range(0.0)
......@@ -77,18 +322,14 @@ class CircuitDataBaseTest():
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("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_os_h = np.array(self.plc_os.get_horizontal_routing_congestion())
print(temp_gl_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("OS H Congestion: ", temp_os_h)
print("GL H Congestion: ", self.plc.get_horizontal_routing_congestion())
print("OS H Congestion: ", self.plc_os.get_horizontal_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())
......@@ -133,140 +374,44 @@ class CircuitDataBaseTest():
####################################################################### BY ENTRY
print("**************BY ENTRY DIFF")
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)
for plc_adj, plcos_adj in zip(plc_clusteradj, plcos_clusteradj):
assert(np.sum(np.nonzero(plc_adj - plcos_adj)) == 0)
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 main(argv):
args = sys.argv[1:]
temp = CircuitDataBaseTest(args[0])
temp.test_proxy_congestion()
# temp.test_proxy_cost()
# temp.test_metadata()
# temp.test_miscellaneous()
if __name__ == "__main__":
app.run(main)
\ No newline at end of file
def parse_flags(argv):
parser = argparse_flags.ArgumentParser(description='An argparse + app.run example')
parser.add_argument("--netlist", required=True,
help="Path to netlist in pb.txt")
parser.add_argument("--plc", required=False,
help="Path to plc in .plc")
parser.add_argument("--width", type=float, required=True,
help="Canvas width")
parser.add_argument("--height", type=float, required=True,
help="Canvas height")
parser.add_argument("--col", type=int, required=True,
help="Grid column")
parser.add_argument("--row", type=int, required=True,
help="Grid row")
parser.add_argument("--rpmh", type=float, default=10, required=False,
help="Grid row")
parser.add_argument("--rpmv", type=float, default=10, required=False,
help="Grid row")
parser.add_argument("--marh", type=float, default=10, required=False,
help="Grid row")
parser.add_argument("--marv", type=float, default=10, required=False,
help="Grid row")
parser.add_argument("--smooth", type=float, default=1, required=False,
help="Grid row")
return parser.parse_args(argv[1:])
def main(args):
if args.plc:
PCT = PlacementCostTest(args.netlist, args.plc, args.width, args.height,
args.col, args.row, args.rpmv, args.rpmv,
args.marh, args.marv, args.smooth)
else:
PCT = PlacementCostTest(args.netlist, args.width, args.height,
args.col, args.row, args.rpmv, args.rpmv,
args.marh, args.marv, args.smooth)
PCT.test_metadata()
PCT.test_proxy_cost()
if __name__ == '__main__':
app.run(main, flags_parser=parse_flags)
\ No newline at end of file
......@@ -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.
- [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.
- [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** -->
......@@ -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.
**What can others contribute?**
- Our shopping list includes the following. Please join in!
- force-directed placement (and API): documentation and implementation
- adjacency matrix generation: documentation and implementation
- Our shopping list (updated August 2022) includes the following. Please join in!
- simulated annealing on the gridded canvas: documentation and implementation
- force-directed placement: documentation and implementation
- donated cloud resources (credits) for experimental studies
- 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
- "fakeram" generator for the ASAP7 research PDK
- qrctechfile for NanGate45
- improved "fakeram" generator for the ASAP7 research PDK
**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**
......
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