Commit 65cf9f34 by sakundu

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

parents c462529e 8cd04019
......@@ -25,6 +25,18 @@ python -m Plc_client.plc_client_os_test --netlist ./Plc_client/test/ariane/netli
--smooth 2
```
You may uncomment any available tests and even run your own test dataset. We do not handle all corner cases since during RL placement, they are unlikely to occur. Our aim here is to reproduce Google's code as much as possible and be able to plug into Circuit Training Flow.
## How to run our code in Circuit Training?
Once you have downloaded Google's Circuit Training code, replace the environment.py with environment_ct.py (**you do need to change the name of the file**). Then, copy `plc_client_os.py` under the same directory (**you should not replace it with `plc_client.py` and should not change the name of the file**).
Since Force Directed Placer for the soft macros is not implemented yet, our code is essentially running Google's `plc_client.py` in parallel with our `plc_client_os.py` but extracting input from our code only except for soft macro positions. The memory usage will double and the runtime tends to be longer. However, with this "more open sourced" version of Circuit Training, we do see comparable training quality as using Google's API.
If you wish to find any discrepancies between these outputs, toggle `DEBUG` to `True` [here](https://github.com/TILOS-AI-Institute/MacroPlacement/blob/e634766f6aa53510c3fe8062896a6020f7ff18d1/CodeElements/Plc_client/environment_ct.py#L42) at the beginning of `environment_ct.py`. This will save all discrepancies into the corresponding folders.
## Implementation Details
For complete information on how the proxy cost is computed in our code, please refer to [Proxy Cost Documentation](https://tilos-ai-institute.github.io/MacroPlacement/Docs/ProxyCost/). Below is a quick overview of the formulation.
## HPWL Computation
Given a net $i$, its wirelength can be computed as the following:
......@@ -85,11 +97,8 @@ $$
Notice a smoothing range can be set for congestion. This is only applied to congestion due to net routing which by counting adjacent cells and adding the averaged congestion to these adjacent cells. More details are provided in the document above.
## Placement Util
**Disclaimer: We DO NOT own the content of placement_util_os.py. All rights belong to Google Authors. This is a modified version of placement_util.py and we are including in the repo for the sake of testing. Original Code can be viewed [here](https://github.com/google-research/circuit_training/blob/main/circuit_training/environment/placement_util.py)**.
## Observation Extractor
**Disclaimer: We DO NOT own the content of observation_extractor_os.py. All rights belong to Google Authors. This is a modified version of observation_extractor.py and we are including in the repo for the sake of testing. Original Code can be viewed [here](https://github.com/google-research/circuit_training/blob/main/circuit_training/environment/observation_extractor.py)**.
## DISCLAIMER
**We DO NOT own the original content of placement_util_os.py, observation_extractor_os.py, environment_os.py, environment_ct.py, coordinate_descent_placer.py. All rights belong to Google Authors. These are modified version of the original code and we are including in the repo for the sake of testing. Original Code can be viewed [here](https://github.com/google-research/circuit_training/blob/main/circuit_training/environment/placement_util.py)**.
# [We copy and modify the below code from https://github.com/google-research/circuit_training. This is only for testing purposes.]
# 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.
"""Circuit training Environmnet with gin config."""
import datetime
import math
import os
from typing import Any, Callable, Dict, Text, Tuple, Optional
from absl import logging
from circuit_training.environment import coordinate_descent_placer as cd_placer
from circuit_training.environment import observation_config
from circuit_training.environment import observation_extractor
from circuit_training.environment import placement_util
from circuit_training.environment import plc_client
from circuit_training.environment import plc_client_os
import gin
import gym
import numpy as np
import random, sys
import tensorflow as tf
from tf_agents.environments import suite_gym
from tf_agents.environments import wrappers
# for slicing dict
import itertools
ObsType = Dict[Text, np.ndarray]
InfoType = Dict[Text, float]
DEBUG = False
# make save failed directory
if DEBUG:
if not os.path.exists('failed_node_indices'):
os.makedirs('failed_node_indices')
if not os.path.exists('failed_proxy_plc'):
os.makedirs('failed_proxy_plc')
if not os.path.exists('failed_proxy_coord'):
os.makedirs('failed_proxy_coord')
if not os.path.exists('failed_obs'):
os.makedirs('failed_obs')
if not os.path.exists('failed_mask'):
os.makedirs('failed_mask')
class InfeasibleActionError(ValueError):
"""An infeasible action were passed to the env."""
def __init__(self, action, mask):
"""Initialize an infeasible action error.
Args:
action: Infeasible action that was performed.
mask: The mask associated with the current observation. mask[action] is
`0` for infeasible actions.
"""
ValueError.__init__(self, action, mask)
self.action = action
self.mask = mask
def __str__(self):
return 'Infeasible action (%s) when the mask is (%s)' % (self.action,
self.mask)
@gin.configurable
def cost_info_function(
plc: plc_client.PlacementCost,
done: bool,
wirelength_weight: float = 1.0,
density_weight: float = 0.5,
congestion_weight: float = 0.5) -> Tuple[float, Dict[Text, float]]:
"""Returns the RL cost and info.
Args:
plc: Placement cost object.
done: Set if it is the terminal step.
wirelength_weight: Weight of wirelength in the reward function.
density_weight: Weight of density in the reward function.
congestion_weight: Weight of congestion in the reward function used only for
legalizing the placement in greedy std cell placer.
Returns:
The RL cost.
Raises:
ValueError: When the cost mode is not supported.
Notes: we found the default congestion and density weights more stable.
"""
proxy_cost = 0.0
if not done:
return proxy_cost, {
'wirelength': -1.0,
'congestion': -1.0,
'density': -1.0,
}
wirelength = -1.0
congestion = -1.0
density = -1.0
if wirelength_weight > 0.0:
wirelength = plc.get_cost()
proxy_cost += wirelength_weight * wirelength
if congestion_weight > 0.0:
congestion = plc.get_congestion_cost()
proxy_cost += congestion_weight * congestion
if density_weight > 0.0:
density = plc.get_density_cost()
proxy_cost += density_weight * density
info = {
'wirelength': wirelength,
'congestion': congestion,
'density': density,
}
return proxy_cost, info
# OS
@gin.configurable
def cost_info_function_os(
plc_os: plc_client_os.PlacementCost,
done: bool,
wirelength_weight: float = 1.0,
density_weight: float = 0.5,
congestion_weight: float = 0.5) -> Tuple[float, Dict[Text, float]]:
"""Returns the RL cost and info.
Args:
plc: Placement cost object.
done: Set if it is the terminal step.
wirelength_weight: Weight of wirelength in the reward function.
density_weight: Weight of density in the reward function.
congestion_weight: Weight of congestion in the reward function used only for
legalizing the placement in greedy std cell placer.
Returns:
The RL cost.
Raises:
ValueError: When the cost mode is not supported.
Notes: we found the default congestion and density weights more stable.
"""
proxy_cost = 0.0
if not done:
return proxy_cost, {
'wirelength': -1.0,
'congestion': -1.0,
'density': -1.0,
}
wirelength = -1.0
congestion = -1.0
density = -1.0
if wirelength_weight > 0.0:
wirelength = plc_os.get_cost()
proxy_cost += wirelength_weight * wirelength
if congestion_weight > 0.0:
congestion = plc_os.get_congestion_cost()
proxy_cost += congestion_weight * congestion
if density_weight > 0.0:
density = plc_os.get_density_cost()
proxy_cost += density_weight * density
info = {
'wirelength': wirelength,
'congestion': congestion,
'density': density,
}
return proxy_cost, info
@gin.configurable
class CircuitEnv(object):
"""Defines the CircuitEnv class."""
INFEASIBLE_REWARD = -1.0
def __init__(
self,
netlist_file: Text = '',
init_placement: Text = '',
create_placement_cost_fn: Callable[
..., plc_client.PlacementCost] = placement_util.create_placement_cost,
std_cell_placer_mode: Text = 'fd',
cost_info_fn: Callable[[plc_client.PlacementCost, bool],
Tuple[float, Dict[Text,
float]]] = cost_info_function,
global_seed: int = 0,
is_eval: bool = False,
save_best_cost: bool = False,
output_plc_file: Text = '',
make_soft_macros_square: bool = True,
cd_finetune: bool = False,
cd_plc_file: Text = 'ppo_cd_placement.plc',
train_step: Optional[tf.Variable] = None,
unplace_all_nodes_in_init: bool = True):
"""Creates a CircuitEnv.
Args:
netlist_file: Path to the input netlist file.
init_placement: Path to the input inital placement file, used to read grid
and canas size.
create_placement_cost_fn: A function that given the netlist and initial
placement file create the placement_cost object.
std_cell_placer_mode: Options for fast std cells placement: `fd` (uses the
force-directed algorithm).
cost_info_fn: The cost function that given the plc object returns the RL
cost.
global_seed: Global seed for initializing env features. This seed should
be the same across actors. Not used currently.
is_eval: If set, save the final placement in output_dir.
save_best_cost: Boolean, if set, saves the palcement if its cost is better
than the previously saved palcement.
output_plc_file: The path to save the final placement.
make_soft_macros_square: If True, make the shape of soft macros square
before using analytical std cell placers like FD.
cd_finetune: If True, runs coordinate descent to finetune macro
orientations. Supposed to run in eval only, not training.
cd_plc_file: Name of the CD fine-tuned plc file, the file will be save in
the same dir as output_plc_file
train_step: A tf.Variable indicating the training step, only used for
saving plc files in the evaluation.
unplace_all_nodes_in_init: Unplace all nodes after initialization.
"""
del global_seed
if not netlist_file:
raise ValueError('netlist_file must be provided.')
self.netlist_file = netlist_file
self._std_cell_placer_mode = std_cell_placer_mode
self._cost_info_fn = cost_info_fn
self._cost_info_fn_os = cost_info_function_os # OS
self._is_eval = is_eval
self._save_best_cost = save_best_cost
self._output_plc_file = output_plc_file
self._output_plc_dir = os.path.dirname(output_plc_file)
self._make_soft_macros_square = make_soft_macros_square
self._cd_finetune = cd_finetune
self._cd_plc_file = cd_plc_file
self._train_step = train_step
self._plc = create_placement_cost_fn(
netlist_file=netlist_file, init_placement=init_placement)
# OS
self._plc_os = placement_util.create_placement_cost_os(
netlist_file=netlist_file, init_placement=init_placement)
# OS
self._hash = -1
# We call ObservationExtractor before unplace_all_nodes, so we use the
# inital placement in the static features (location_x and location_y).
# This results in better placements.
self._observation_config = observation_config.ObservationConfig()
self._observation_extractor = observation_extractor.ObservationExtractor(
plc=self._plc)
# OS
self._observation_extractor_os = observation_extractor.ObservationExtractor(
plc=self._plc_os)
if self._make_soft_macros_square:
# It is better to make the shape of soft macros square before using
# analytical std cell placers like FD.
self._plc.make_soft_macros_square()
self._grid_cols, self._grid_rows = self._plc.get_grid_num_columns_rows()
self._canvas_width, self._canvas_height = self._plc.get_canvas_width_height(
)
# OS
self._grid_cols, self._grid_rows = self._plc_os.get_grid_num_columns_rows()
self._canvas_width, self._canvas_height = self._plc_os.get_canvas_width_height(
)
self._hard_macro_indices = [
m for m in self._plc.get_macro_indices()
if not self._plc.is_node_soft_macro(m)
]
# OS
self._hard_macro_indices_os = [
m for m in self._plc_os.get_macro_indices()
if not self._plc_os.is_node_soft_macro(m)
]
if DEBUG and not (np.array(self._hard_macro_indices) == np.array(self._hard_macro_indices_os)).all():
logging.info('*****DISCREPENCY FOUND IN HARD MACRO INDICES*****')
with open('./failed_node_indices/hard_macro_indices_{}.npy'.format(str(self._hash)), 'wb') as f:
# GL
np.save(f, np.array(self._hard_macro_indices))
# OS
np.save(f, np.array(self._hard_macro_indices_os))
else:
logging.info('* hard macro indices matched *')
self._num_hard_macros = len(self._hard_macro_indices_os)
self._sorted_node_indices = placement_util.get_ordered_node_indices(
mode='descending_size_macro_first', plc=self._plc)
# OS
self._sorted_node_indices_os = placement_util.get_ordered_node_indices(
mode='descending_size_macro_first', plc=self._plc_os)
if DEBUG and not (np.array(self._sorted_node_indices_os) == np.array(self._sorted_node_indices)).all():
logging.info('*****DISCREPENCY FOUND IN NODE_INDICES*****')
with open('./failed_node_indices/sorted_indices_{}.npy'.format(str(self._hash)), 'wb') as f:
# GL
np.save(f, np.array(self._sorted_node_indices))
# OS
np.save(f, np.array(self._sorted_node_indices_os))
else:
logging.info('* node indices matched *')
self._sorted_soft_macros = self._sorted_node_indices_os[self._num_hard_macros:]
# Generate a map from actual macro_index to its position in
# self.macro_indices. Needed because node adjacency matrix is in the same
# node order of plc.get_macro_indices.
self._macro_index_to_pos = {}
# for i, macro_index in enumerate(self._plc.get_macro_indices()):
# self._macro_index_to_pos[macro_index] = i
# OS
for i, (macro_index, macro_index_os) in enumerate(zip(self._plc.get_macro_indices(), self._plc_os.get_macro_indices())):
if DEBUG and macro_index != macro_index_os:
logging.info('*****DISCREPENCY FOUND IN MACRO_INDEX*****')
with open('./failed_macro_index.txt', 'a+') as f:
f.write("[hash:{}] at {}, gl: {}, os: {}".format(str(self._hash), str(i), str(macro_index), str(macro_index_os),'\n'))
self._macro_index_to_pos[macro_index_os] = i
# Padding for mapping the placement canvas on the agent canvas.
rows_pad = self._observation_config.max_grid_size - self._grid_rows
cols_pad = self._observation_config.max_grid_size - self._grid_cols
self._up_pad = rows_pad // 2
self._right_pad = cols_pad // 2
self._low_pad = rows_pad - self._up_pad
self._left_pad = cols_pad - self._right_pad
self._saved_cost = np.inf
self._current_actions = []
self._current_node = 0
self._done = False
# OOM
# self._current_mask = self._get_mask()
# OS
self._current_mask_os = self._get_mask_os()
# Discrep Detection
# if not (np.array(self._current_mask) == np.array(self._current_mask_os)).all():
# logging.info('*****DISCREPENCY FOUND IN CURRENT MASK*****')
# with open('./init_mask/run{}_node_{}.npy'.format(str(self._hash), str(self._current_node)), 'wb') as f:
# # GL
# np.save(f, np.array(self._current_mask))
# # OS
# np.save(f, np.array(self._current_mask_os))
# else:
# logging.info('* node mask matched *')
if unplace_all_nodes_in_init:
# TODO(b/223026568) Remove unplace_all_nodes from init
self._plc.unplace_all_nodes()
# OS
self._plc_os.unplace_all_nodes()
logging.warning('* Unplaced all Nodes in init *')
logging.info('***Num node to place***:%s', self._num_hard_macros)
@property
def observation_space(self) -> gym.spaces.Space:
"""Env Observation space."""
return self._observation_config.observation_space
@property
def action_space(self) -> gym.spaces.Space:
return gym.spaces.Discrete(self._observation_config.max_grid_size**2)
@property
def environment_name(self) -> Text:
return self.netlist_file
def get_static_obs(self):
"""Get the static observation for the environment.
Static observations are invariant across steps on the same netlist, such as
netlist metadata and the adj graphs. This should only be used for
generalized RL.
Returns:
Numpy array representing the observation
"""
return self._observation_extractor.get_static_features()
# This is not used anywhere
def get_cost_info(self,
done: bool = False) -> Tuple[float, Dict[Text, float]]:
return self._cost_info_fn(plc=self._plc, done=done) # pytype: disable=wrong-keyword-args # trace-all-classes
def _get_mask(self) -> np.ndarray:
"""Gets the node mask for the current node.
Returns:
List of 0s and 1s indicating if action is feasible or not.
"""
if self._done:
mask = np.zeros(self._observation_config.max_grid_size**2, dtype=np.int32)
else:
node_index = self._sorted_node_indices[self._current_node]
mask = np.asarray(self._plc.get_node_mask(node_index), dtype=np.int32)
mask = np.reshape(mask, [self._grid_rows, self._grid_cols])
pad = ((self._up_pad, self._low_pad), (self._right_pad, self._left_pad))
mask = np.pad(mask, pad, mode='constant', constant_values=0)
return np.reshape(
mask, (self._observation_config.max_grid_size**2,)).astype(np.int32)
# OS
def _get_mask_os(self) -> np.ndarray:
"""Gets the node mask for the current node.
Returns:
List of 0s and 1s indicating if action is feasible or not.
"""
if self._done:
mask = np.zeros(self._observation_config.max_grid_size**2, dtype=np.int32)
else:
node_index = self._sorted_node_indices_os[self._current_node]
mask = np.asarray(self._plc_os.get_node_mask(node_index), dtype=np.int32)
mask = np.reshape(mask, [self._grid_rows, self._grid_cols])
pad = ((self._up_pad, self._low_pad), (self._right_pad, self._left_pad))
mask = np.pad(mask, pad, mode='constant', constant_values=0)
return np.reshape(
mask, (self._observation_config.max_grid_size**2,)).astype(np.int32)
def _get_obs(self) -> ObsType:
"""Returns the observation."""
if self._current_node > 0:
previous_node_sorted = self._sorted_node_indices[self._current_node - 1]
previous_node_index = self._macro_index_to_pos[previous_node_sorted]
else:
previous_node_index = -1
if self._current_node < self._num_hard_macros:
current_node_sorted = self._sorted_node_indices[self._current_node]
current_node_index = self._macro_index_to_pos[current_node_sorted]
else:
current_node_index = 0
return self._observation_extractor.get_all_features(
previous_node_index=previous_node_index,
current_node_index=current_node_index,
mask=self._current_mask)
# OS
def _get_obs_os(self) -> ObsType:
"""Returns the observation."""
if self._current_node > 0:
previous_node_sorted = self._sorted_node_indices_os[self._current_node - 1]
previous_node_index = self._macro_index_to_pos[previous_node_sorted]
else:
previous_node_index = -1
if self._current_node < self._num_hard_macros:
current_node_sorted = self._sorted_node_indices_os[self._current_node]
current_node_index = self._macro_index_to_pos[current_node_sorted]
else:
current_node_index = 0
return self._observation_extractor_os.get_all_features(
previous_node_index=previous_node_index,
current_node_index=current_node_index,
mask=self._current_mask_os)
def _run_cd(self):
"""Runs coordinate descent to finetune the current placement."""
# CD only modifies macro orientation.
# Plc modified by CD will be reset at the end of the episode.
def cost_fn(plc):
return self._cost_info_fn(plc=plc, done=True) # pytype: disable=wrong-keyword-args # trace-all-classes
cd = cd_placer.CoordinateDescentPlacer(
plc=self._plc,
cost_fn=cost_fn,
use_stdcell_placer=True,
optimize_only_orientation=True)
cd.place()
def _save_placement(self, cost: float) -> None:
"""Saves the current placement.
Args:
cost: the current placement cost.
Raises:
IOError: If we cannot write the placement to file.
"""
if not self._save_best_cost or (cost < self._saved_cost and
(math.fabs(cost - self._saved_cost) /
(cost) > 5e-3)):
user_comments = ''
if self._train_step:
user_comments = f'Train step : {self._train_step.numpy()}'
placement_util.save_placement(self._plc, self._output_plc_file,
user_comments)
ts = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
ppo_snapshot_file = os.path.join(
self._output_plc_dir,
f'snapshot_ppo_opt_placement_timestamp_{ts}_cost_{cost:.4f}.plc')
placement_util.save_placement(self._plc, ppo_snapshot_file, user_comments)
self._saved_cost = cost
# Only runs CD if this is the best RL placement seen so far.
if self._cd_finetune:
self._run_cd()
cost = self._cost_info_fn(plc=self._plc, done=True)[0] # pytype: disable=wrong-keyword-args # trace-all-classes
cd_plc_file = os.path.join(self._output_plc_dir, self._cd_plc_file)
placement_util.save_placement(self._plc, cd_plc_file, user_comments)
cd_snapshot_file = os.path.join(
self._output_plc_dir,
f'snapshot_ppo_cd_placement_timestamp_{ts}_cost_{cost:.4f}.plc')
placement_util.save_placement(self._plc, cd_snapshot_file,
user_comments)
def call_analytical_placer_and_get_cost(self) -> tuple[float, InfoType]:
"""Calls analytical placer.
Calls analystical placer and evaluates cost when all nodes are placed. Also,
saves the placement file for eval if all the macros are placed by RL.
Returns:
A tuple for placement cost and info.
"""
if self._done:
self.analytical_placer()
# Only evaluates placement cost when all nodes are placed.
# All samples in the episode receive the same reward equal to final cost.
# This is realized by setting intermediate steps cost as zero, and
# propagate the final cost with discount factor set to 1 in replay buffer.
cost, info = self._cost_info_fn(self._plc, self._done)
# OS
for node_index in placement_util.nodes_of_types(self._plc, ['MACRO']):
if self._plc.is_node_soft_macro(node_index):
x_pos, y_pos = self._plc.get_node_location(node_index)
self._plc_os.set_soft_macro_position(node_index, x_pos, y_pos)
cost_os, info_os = self._cost_info_fn(self._plc_os, self._done)
# Discrep Detection
if DEBUG and abs(cost_os - cost) >= 1e-2 and self._current_node == self._num_hard_macros:
logging.info('*****DISCREPENCY FOUND IN PROXY COST*****')
cd_plc_file = './failed_proxy_plc/' + str(cost) + '_vs_' + str(cost_os)
comment = '***GL***\ncongestion cost:{}\nwirelength cost:{}\ndensity cost:{}\n'\
.format(self._plc.get_congestion_cost(), self._plc.get_cost(), self._plc.get_density_cost())
comment += 'canvas_width_height:{}' + str(self._plc.get_canvas_width_height())
comment += 'get_grid_num_columns_rows:{}' + str(self._plc.get_grid_num_columns_rows())
comment += '\n***OS***\ncongestion cost:{}\nwirelength cost:{}\ndensity cost:{}\n'\
.format(self._plc_os.get_congestion_cost(), self._plc_os.get_cost(), self._plc_os.get_density_cost())
placement_util.save_placement(self._plc, cd_plc_file, comment)
placement_util.save_placement(self._plc_os, cd_plc_file+"os", comment)
# also save all coordinate
with open('./failed_proxy_coord/{}_vs_{}.npy'.format(str(cost), str(cost_os)), 'wb') as f:
# GL
np.save(f, np.array(list(placement_util.get_node_xy_coordinates(self._plc).items())))
# OS
np.save(f, np.array(list(placement_util.get_node_xy_coordinates(self._plc_os).items())))
else:
logging.info('* proxy cost matched *')
# We only save placement if all nodes by placed RL, because the dreamplace
# mix-sized placement may not be legal.
if self._current_node == self._num_hard_macros and self._is_eval:
self._save_placement(cost)
return -cost_os, info
def reset(self) -> ObsType:
"""Resets the environment.
Returns:
An initial observation.
"""
self._hash = random.randint(0, sys.maxsize)
self._plc.unplace_all_nodes()
#OS
self._plc_os.unplace_all_nodes()
self._current_actions = []
self._current_node = 0
self._done = False
# OOM
self._current_mask = self._get_mask()
self._current_mask_os = self._get_mask_os()
# OOM
obs = self._get_obs()
obs_os = self._get_obs_os()
if DEBUG:
for feature_gl, feature_os in zip(obs, obs_os):
if not (obs[feature_gl] == obs_os[feature_os]).all():
logging.info('*****DISCREPENCY FOUND IN OBSERVATION*****')
with open('./failed_obs/reset_{}_feature_{}.npy'.format(str(self._hash), str(feature_gl)+'@'+str(feature_os)), 'wb') as f:
# GL
np.save(f, np.array(obs[feature_gl]))
# OS
np.save(f, np.array(obs_os[feature_os]))
return obs_os
def translate_to_original_canvas(self, action: int) -> int:
"""Translates a raw location to real one in the original canvas."""
up_pad = (self._observation_config.max_grid_size - self._grid_rows) // 2
right_pad = (self._observation_config.max_grid_size - self._grid_cols) // 2
a_i = action // self._observation_config.max_grid_size - up_pad
a_j = action % self._observation_config.max_grid_size - right_pad
if 0 <= a_i < self._grid_rows or 0 <= a_j < self._grid_cols:
action = a_i * self._grid_cols + a_j
else:
#OS
raise InfeasibleActionError(action, self._current_mask_os)
return action
def place_node(self, node_index: int, action: int) -> None:
print(">>>>GL: " + str(self.translate_to_original_canvas(action)))
self._plc.place_node(node_index, self.translate_to_original_canvas(action))
#OS
print(">>>>OS: " + str(self.translate_to_original_canvas(action)))
self._plc_os.place_node(node_index, self.translate_to_original_canvas(action))
# print(">>>>GL Placed {}: {}, OS Placed {}: {}".format(str(node_index), str(self._plc.get_node_location(node_index)), str(node_index), str(self._plc_os.get_node_location(node_index))))
def analytical_placer(self) -> None:
if self._std_cell_placer_mode == 'fd':
placement_util.fd_placement_schedule(self._plc)
else:
raise ValueError('%s is not a supported std_cell_placer_mode.' %
(self._std_cell_placer_mode))
def step(self, action: int) -> Tuple[ObsType, float, bool, Any]:
"""Steps the environment.
Args:
action: The action to take (should be a list of size 1).
Returns:
observation, reward, done, and info.
Raises:
RuntimeError: action taken after episode was done
InfeasibleActionError: bad action taken (action is not in feasible
actions)
"""
if self._done:
raise RuntimeError('Action taken after episode is done.')
action = int(action)
self._current_actions.append(action)
if self._current_mask_os[action] == 0:
raise InfeasibleActionError(action, self._current_mask_os)
node_index = self._sorted_node_indices_os[self._current_node]
self.place_node(node_index, action) # OS place at the same time
self._current_node += 1
self._done = (self._current_node == self._num_hard_macros)
self._current_mask = self._get_mask()
self._current_mask_os = self._get_mask_os() # OS
# Discrep Detection
if DEBUG and not (np.array(self._current_mask) == np.array(self._current_mask_os)).all():
logging.info('*****DISCREPENCY FOUND IN CURRENT MASK*****')
with open('./failed_mask/action_{}_node_{}.npy'.format(str(action), str(node_index)), 'wb') as f:
# GL
np.save(f, np.array(self._current_mask))
# OS
np.save(f, np.array(self._current_mask_os))
else:
logging.info('* node mask matched *')
if not self._done and not np.any(self._current_mask_os):
logging.info('Actions took before becoming infeasible: %s',
self._current_actions)
info = {
'wirelength': -1.0,
'congestion': -1.0,
'density': -1.0,
}
return self.reset(), self.INFEASIBLE_REWARD, True, info
cost, info = self.call_analytical_placer_and_get_cost()
# OS
# OOM
obs = self._get_obs()
obs_os = self._get_obs_os()
if DEBUG:
for feature_gl, feature_os in zip(obs, obs_os):
if not (obs[feature_gl] == obs_os[feature_os]).all() and not _done:
logging.info('*****DISCREPENCY FOUND IN OBSERVATION*****')
with open('./failed_obs/step_{}_feature_{}.npy'.format(str(self._hash), str(feature_gl)+'@'+str(feature_os)), 'wb') as f:
# GL
np.save(f, np.array(obs[feature_gl]))
# OS
np.save(f, np.array(obs_os[feature_os]))
return obs_os, cost, self._done, info
def create_circuit_environment(*args, **kwarg) -> wrappers.ActionClipWrapper:
"""Create an `CircuitEnv` wrapped as a Gym environment.
Args:
*args: Arguments.
**kwarg: keyworded Arguments.
Returns:
PyEnvironment used for training.
"""
env = CircuitEnv(*args, **kwarg)
return wrappers.ActionClipWrapper(suite_gym.wrap_env(env))
......@@ -179,7 +179,7 @@ class ObservationExtractor(object):
sparse_adj_weight.append(weight)
edge_counts[i] += 1
edge_counts[j] += 1
features['sparse_adj_i'] = np.asarray(sparse_adj_i).astype(np.int32)
features['sparse_adj_j'] = np.asarray(sparse_adj_j).astype(np.int32)
features['sparse_adj_weight'] = np.asarray(sparse_adj_weight).astype(
......@@ -203,15 +203,12 @@ class ObservationExtractor(object):
def _get_clustered_port_locations(
self, grid_cell_index: int) -> Tuple[float, float]:
"""Returns clustered port locations.
This function returns an approximation location of the ports in a grid
cell. Depending on the cell location in the canvas, the approximation
differs.
Args:
grid_cell_index: The index of the grid cell where the cluster port is
located.
Returns:
A tuple of float: Approximate x, y location of the port cluster in the
grid cell in the same unit as canvas width and height (micron).
......@@ -403,4 +400,4 @@ class ObservationExtractor(object):
previous_node_index=previous_node_index,
current_node_index=current_node_index,
mask=mask))
return features
return features
\ No newline at end of file
......@@ -13,7 +13,6 @@
# 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.
"""
......@@ -80,11 +79,9 @@ def restore_macro_orientations(plc: plc_client.PlacementCost,
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.
"""
......@@ -134,10 +131,8 @@ def get_blockages_from_comments(
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
"""
......@@ -174,7 +169,6 @@ def extract_sizes_from_comments(
# done
def fix_port_coordinates(plc: plc_client.PlacementCost) -> None:
"""Find all ports and fix their coordinates.
Args:
plc: the placement cost object.
"""
......@@ -204,7 +198,6 @@ def create_placement_cost(
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.
......@@ -218,7 +211,6 @@ def create_placement_cost(
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.
"""
......@@ -272,10 +264,8 @@ def create_placement_cost(
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.
"""
......@@ -391,7 +381,6 @@ def fd_placement_schedule(plc: plc_client.PlacementCost,
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.
......@@ -425,12 +414,10 @@ 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.
"""
......@@ -465,10 +452,8 @@ def get_ordered_node_indices(mode: str,
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
"""
......@@ -503,9 +488,7 @@ def extract_parameters_from_comments(
# done
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.
"""
......@@ -703,7 +686,6 @@ def save_placement_with_info(plc: plc_client.PlacementCost,
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}\
......@@ -907,12 +889,10 @@ def grid_locations_near(plc: plc_client.PlacementCost,
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.
"""
......@@ -983,7 +963,7 @@ def main():
NODE_XY_DICT = {}
for i in nodes_of_types(plc, ['MACRO', 'macro', 'STDCELL', 'PORT']):
NODE_XY_DICT[i] = (100, 100)
restore_node_xy_coordinates(plc, NODE_XY_DICT)
# print(get_node_xy_coordinates(plc))
......@@ -991,7 +971,7 @@ def main():
MACRO_ORIENTATION = {}
for i in nodes_of_types(plc, ['MACRO', 'macro']):
MACRO_ORIENTATION[i] = "S"
restore_macro_orientations(plc, MACRO_ORIENTATION)
# print(get_macro_orientations(plc))
......
......@@ -74,15 +74,18 @@ class PlacementCost(object):
# All modules look-up table
self.modules = []
self.modules_w_pins = []
# modules to index look-up table
self.indices_to_mod_name = {}
self.mod_name_to_indices = {}
# indices storage
self.port_indices = []
self.hard_macro_indices = []
self.hard_macro_pin_indices = []
self.soft_macro_indices = []
self.soft_macro_pin_indices = []
# macro to pins look-up table: [MACRO_NAME] => [PIN_NAME]
self.hard_macros_to_inpins = {}
self.soft_macros_to_inpins = {}
......@@ -90,7 +93,7 @@ class PlacementCost(object):
# Placed macro
self.placed_macro = []
# unknown
# not used
self.use_incremental_cost = False
# blockage
self.blockages = []
......@@ -100,11 +103,12 @@ class PlacementCost(object):
# default canvas width/height based on cell area
self.width = math.sqrt(self.get_area()/0.6)
self.height = math.sqrt(self.get_area()/0.6)
# default gridding
self.grid_col = 10
self.grid_row = 10
# initialize congestion map
# TODO recompute after new gridding
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)
......@@ -112,6 +116,7 @@ class PlacementCost(object):
# initial grid mask, flatten before output
self.node_mask = np.array([1] * (self.grid_col * self.grid_row))\
.reshape(self.grid_row, self.grid_col)
# store module/component count
self.ports_cnt = len(self.port_indices)
self.hard_macro_cnt = len(self.hard_macro_indices)
......@@ -119,6 +124,7 @@ class PlacementCost(object):
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) - \
......@@ -136,7 +142,7 @@ class PlacementCost(object):
def __read_protobuf(self):
"""
Protobuf Netlist Parser
private function: Protobuf Netlist Parser
"""
with open(self.netlist_file) as fp:
line = fp.readline()
......@@ -159,7 +165,6 @@ class PlacementCost(object):
# node found
if line_item[0] == 'node':
node_cnt += 1
node_name = ''
input_list = []
......@@ -169,8 +174,14 @@ class PlacementCost(object):
# retrieve node name
if line_item[0] == 'name':
node_name = line_item[1]
# skip metadata header
if node_name == "__metadata__":
pass
else:
node_cnt += 1
else:
node_name = 'N/A name'
# advance ptr
line = fp.readline()
......@@ -198,34 +209,54 @@ class PlacementCost(object):
line_item = re.findall(r'\w+', line)
key = line_item[1]
# advance, expect value
line = fp.readline()
line_item = re.findall(r'\w+', line)
if key == "macro_name":
# advance, expect value
line = fp.readline()
line_item = re.findall(r'\w+', line)
# advance, expect value item
line = fp.readline()
line_item = re.findall(r'\-*\w+\.*\/{0,1}\w*[\w+\/{0,1}\w*]*', line)
# advance, expect value item
line = fp.readline()
line_item = re.findall(r'\w+[^\:\n\\{\}\s"]*', line)
attr_dict[key] = line_item
attr_dict[key] = line_item
line = fp.readline()
line = fp.readline()
line = fp.readline()
line = fp.readline()
line = fp.readline()
line = fp.readline()
line_item = re.findall(r'\w+', line)
line_item = re.findall(r'\w+', line)
else:
# advance, expect value
line = fp.readline()
line_item = re.findall(r'\w+', line)
if attr_dict['type'][1] == 'macro':
# advance, expect value item
line = fp.readline()
line_item = re.findall(r'\-*\w+\.*\/{0,1}\w*[\w+\/{0,1}\w*]*', line)
attr_dict[key] = line_item
line = fp.readline()
line = fp.readline()
line = fp.readline()
line_item = re.findall(r'\w+', line)
if node_name == "__metadata__":
# skipping metadata header
logging.info('[INFO NETLIST PARSER] skipping invalid net input')
elif attr_dict['type'][1] == 'macro':
# soft macro
# check if all required information is obtained
try:
assert 'x' in attr_dict.keys()
except AssertionError:
logging.warning('[NETLIST PARSER ERROR] x is not defined')
logging.warning('[ERROR NETLIST PARSER] x is not defined')
try:
assert 'y' in attr_dict.keys()
except AssertionError:
logging.warning('[NETLIST PARSER ERROR] y is not defined')
logging.warning('[ERROR NETLIST PARSER] y is not defined')
soft_macro = self.SoftMacro(name=node_name, width=attr_dict['width'][1],
height = attr_dict['height'][1],
......@@ -248,15 +279,17 @@ class PlacementCost(object):
y = attr_dict['y'][1],
macro_name = attr_dict['macro_name'][1])
if 'weight' in attr_dict.keys():
soft_macro_pin.set_weight(float(attr_dict['weight'][1]))
# if pin has net info
if input_list:
# net count should be factored by net weight
if 'weight' in attr_dict.keys():
self.net_cnt += 1 * float(attr_dict['weight'][1])
else:
self.net_cnt += 1
soft_macro_pin.add_sinks(input_list)
if 'weight' in attr_dict.keys():
soft_macro_pin.set_weight(float(attr_dict['weight'][1]))
self.modules_w_pins.append(soft_macro_pin)
# mapping node_name ==> node idx
......@@ -301,10 +334,14 @@ class PlacementCost(object):
x_offset = attr_dict['x_offset'][1],
y_offset = attr_dict['y_offset'][1],
macro_name = attr_dict['macro_name'][1])
# if net weight is defined, set weight
if 'weight' in attr_dict.keys():
hard_macro_pin.set_weight(float(attr_dict['weight'][1]))
# if pin has net info
if input_list:
# net count should be factored by net weight
if 'weight' in attr_dict.keys():
self.net_cnt += 1 * float(attr_dict['weight'][1])
else:
......@@ -334,6 +371,7 @@ class PlacementCost(object):
y = attr_dict['y'][1],
side = attr_dict['side'][1])
# if pin has net info
if input_list:
self.net_cnt += 1
port.add_sinks(input_list)
......@@ -512,7 +550,7 @@ class PlacementCost(object):
traceback.print_tb(tb)
tb_info = traceback.extract_tb(tb)
_, line, _, text = tb_info[-1]
print('[NETLIST/PLC MISMATCH ERROR] at line {} in statement {}'\
print('[ERROR NETLIST/PLC MISMATCH] at line {} in statement {}'\
.format(line, text))
exit(1)
......@@ -522,7 +560,7 @@ class PlacementCost(object):
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 +\
print('[ERROR PLC INDICES MISMATCH]', len(sorted(self.port_indices +\
self.hard_macro_indices +\
self.soft_macro_indices)), len(list(info_dict['node_plc'].keys())))
exit(1)
......@@ -535,7 +573,7 @@ class PlacementCost(object):
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))
print('[ERROR PLC PARSER] %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..
......@@ -573,19 +611,21 @@ class PlacementCost(object):
macro = self.modules_w_pins[macro_idx]
macro_name = macro.get_name()
# Hard macro
if not self.is_node_soft_macro(macro_idx):
if macro_name in self.hard_macros_to_inpins.keys():
pin_names = self.hard_macros_to_inpins[macro_name]
else:
print("[ERROR UPDATE CONNECTION] MACRO not found")
exit(1)
# use is_node_soft_macro()
print("[ERROR UPDATE CONNECTION] MACRO pins not found")
continue
# Soft macro
elif self.is_node_soft_macro(macro_idx):
if macro_name in self.soft_macros_to_inpins.keys():
pin_names = self.soft_macros_to_inpins[macro_name]
else:
print("[ERROR UPDATE CONNECTION] MACRO not found")
exit(1)
print("[ERROR UPDATE CONNECTION] macro pins not found")
continue
for pin_name in pin_names:
pin = self.modules_w_pins[self.mod_name_to_indices[pin_name]]
......@@ -640,8 +680,10 @@ class PlacementCost(object):
def __get_pin_position(self, pin_idx):
"""
private function for getting pin location
* PORT = its own position
* MACRO PIN = ref position + offset position87654321
* HARD MACRO PIN = ref position + offset position
* SOFT MACRO PIN = ref position
"""
try:
assert (self.modules_w_pins[pin_idx].get_type() == 'MACRO_PIN' or\
......@@ -650,19 +692,21 @@ class PlacementCost(object):
print("[ERROR PIN POSITION] Not a MACRO PIN")
exit(1)
# Retrieve node that this pin instantiated on
ref_node_idx = self.get_ref_node_id(pin_idx)
if ref_node_idx == -1:
if self.modules_w_pins[pin_idx].get_type() == 'PORT':
return self.modules_w_pins[pin_idx].get_pos()
else:
# cannot be 'MACRO'
print("[ERROR PIN POSITION] Parent Node Not Found.")
exit(1)
# print("ref_node_idx", ref_node_idx)
# Parent node
ref_node = self.modules_w_pins[ref_node_idx]
ref_node_x, ref_node_y = ref_node.get_pos()
# Retrieve current pin node position
pin_node = self.modules_w_pins[pin_idx]
pin_node_x_offset, pin_node_y_offset = pin_node.get_offset()
......@@ -685,18 +729,26 @@ class PlacementCost(object):
# NOTE: connection only defined on PORT, soft/hard macro pins
if curr_type == "PORT" and mod.get_sink():
# add source position
x_coord.append(self.__get_pin_position(mod_idx)[0])
y_coord.append(self.__get_pin_position(mod_idx)[1])
x_coord.append(mod.get_pos()[0])
y_coord.append(mod.get_pos()[1])
# get sink
for sink_name in mod.get_sink():
for sink_pin in mod.get_sink()[sink_name]:
# retrieve indx in modules_w_pins
sink_idx = self.mod_name_to_indices[sink_pin]
# retrieve sink object
sink = self.modules_w_pins[sink_idx]
# only consider placed sink
ref_sink = self.modules_w_pins[self.get_ref_node_id(sink_idx)]
if not ref_sink.get_placed_flag():
continue
# retrieve location
x_coord.append(self.__get_pin_position(sink_idx)[0])
y_coord.append(self.__get_pin_position(sink_idx)[1])
elif curr_type == "MACRO_PIN":
ref_mod = self.modules_w_pins[self.get_ref_node_id(mod_idx)]
if not ref_mod.get_placed_flag():
continue
# add source position
x_coord.append(self.__get_pin_position(mod_idx)[0])
y_coord.append(self.__get_pin_position(mod_idx)[1])
......@@ -709,7 +761,6 @@ class PlacementCost(object):
# retrieve indx in modules_w_pins
input_idx = self.mod_name_to_indices[sink_name]
# retrieve location
# print(self.__get_pin_position(input_idx))
x_coord.append(self.__get_pin_position(input_idx)[0])
y_coord.append(self.__get_pin_position(input_idx)[1])
......@@ -779,8 +830,9 @@ class PlacementCost(object):
return float(sum_cong / cong_cnt)
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 congestion cost based on routing and macro placement
"""
if self.FLAG_UPDATE_CONGESTION:
self.get_routing()
......@@ -788,7 +840,7 @@ class PlacementCost(object):
def __get_grid_cell_location(self, x_pos, y_pos):
"""
private function for getting grid cell row/col ranging from 0...N
private function: for getting grid cell row/col ranging from 0...N
"""
self.grid_width = float(self.width/self.grid_col)
self.grid_height = float(self.height/self.grid_row)
......@@ -798,7 +850,7 @@ class PlacementCost(object):
def __get_grid_location_position(self, col:int, row:int):
"""
private function for getting x y coord from grid cell row/col
private function: for getting x y coord from grid cell row/col
"""
self.grid_width = float(self.width/self.grid_col)
self.grid_height = float(self.height/self.grid_row)
......@@ -809,7 +861,7 @@ class PlacementCost(object):
def __get_grid_cell_position(self, grid_cell_idx:int):
"""
private function for getting x y coord from grid cell row/col
private function: for getting x y coord from grid cell row/col
"""
row = grid_cell_idx // self.grid_col
col = grid_cell_idx % self.grid_col
......@@ -823,7 +875,7 @@ class PlacementCost(object):
mod_height:float
):
"""
private function for updating node mask after a placement
private function: for updating node mask after a placement
"""
row = grid_cell_idx // self.grid_col
col = grid_cell_idx % self.grid_col
......@@ -835,19 +887,9 @@ class PlacementCost(object):
self.node_mask[ row - ver_pad:row + ver_pad + 1,
col - hor_pad:col + hor_pad + 1] = 0
def __unplace_node_mask(self, grid_cell_idx:int):
"""
private function for updating node mask after unplacing a node
"""
row = grid_cell_idx // self.grid_col
col = grid_cell_idx % self.grid_col
assert row * self.grid_col + col == grid_cell_idx
pass
def __overlap_area(self, block_i, block_j, return_pos=False):
"""
private function for computing block overlapping
private function: for computing block overlapping
"""
x_min_max = min(block_i.x_max, block_j.x_max)
x_max_min = max(block_i.x_min, block_j.x_min)
......@@ -865,7 +907,7 @@ class PlacementCost(object):
def __overlap_dist(self, block_i, block_j):
"""
private function for computing block overlapping
private function: for computing block overlapping
"""
x_diff = min(block_i.x_max, block_j.x_max) - max(block_i.x_min, block_j.x_min)
y_diff = min(block_i.y_max, block_j.y_max) - max(block_i.y_min, block_j.y_min)
......@@ -875,7 +917,7 @@ class PlacementCost(object):
def __add_module_to_grid_cells(self, mod_x, mod_y, mod_w, mod_h):
"""
private function for add module to grid cells
private function: for add module to grid cells
"""
# Two corners
ur = (mod_x + (mod_w/2), mod_y + (mod_h/2))
......@@ -926,6 +968,8 @@ class PlacementCost(object):
self.grid_occupied[self.grid_col * r_i + c_i] += \
self.__overlap_area(grid_cell_block, module_block)
def get_grid_cells_density(self):
"""
......@@ -942,6 +986,11 @@ class PlacementCost(object):
for module_idx in (self.soft_macro_indices + self.hard_macro_indices):
# extract module information
module = self.modules_w_pins[module_idx]
# skipping unplaced module
if not module.get_placed_flag():
continue
module_h = module.get_height()
module_w = module.get_width()
module_x, module_y = module.get_pos()
......@@ -1145,6 +1194,9 @@ class PlacementCost(object):
return self.hrouting_alloc, self.vrouting_alloc
def __two_pin_net_routing(self, source_gcell, node_gcells, weight):
"""
private function: Routing between 2-pin nets
"""
temp_gcell = list(node_gcells)
if temp_gcell[0] == source_gcell:
sink_gcell = temp_gcell[1]
......@@ -1171,7 +1223,10 @@ class PlacementCost(object):
col = sink_gcell[1]
self.V_routing_cong[row * self.grid_col + col] += weight
def l_routing(self, node_gcells, weight):
def __l_routing(self, node_gcells, weight):
"""
private function: L_shape routing in 3-pin nets
"""
node_gcells.sort(key = lambda x: (x[1], x[0]))
y1, x1 = node_gcells[0]
y2, x2 = node_gcells[1]
......@@ -1195,11 +1250,13 @@ class PlacementCost(object):
for row in range(min(y2, y3), max(y2, y3)):
col = x3
self.V_routing_cong[row * self.grid_col + col] += weight
return
def t_routing(self, node_gcells, weight):
def __t_routing(self, node_gcells, weight):
"""
private function: T_shape routing in 3-pin nets
"""
node_gcells.sort()
#print(node_gcells)
y1, x1 = node_gcells[0]
y2, x2 = node_gcells[1]
y3, x3 = node_gcells[2]
......@@ -1220,9 +1277,11 @@ class PlacementCost(object):
for row in range(min(y2, y3), max(y2, y3)):
col = x3
self.V_routing_cong[row * self.grid_col + col] += weight
pass
def __three_pin_net_routing(self, node_gcells, weight):
"""
private_function: Routing Scheme for 3-pin nets
"""
temp_gcell = list(node_gcells)
## Sorted based on X
temp_gcell.sort(key = lambda x: (x[1], x[0]))
......@@ -1231,12 +1290,8 @@ class PlacementCost(object):
y3, x3 = temp_gcell[2]
if x1 < x2 and x2 < x3 and min(y1, y3) < y2 and max(y1, y3) > y2:
# print('sk1')
self.l_routing(temp_gcell, weight)
return
if x2 == x3 and x1 < x2 and y1 < min(y2, y3):
# print('sk2')
self.__l_routing(temp_gcell, weight)
elif x2 == x3 and x1 < x2 and y1 < min(y2, y3):
for col_idx in range(x1,x2,1):
row = y1
col = col_idx
......@@ -1246,10 +1301,7 @@ class PlacementCost(object):
col = x2
row = row_idx
self.V_routing_cong[row * self.grid_col + col] += weight
return
if y2 == y3:
# print('sk3')
elif y2 == y3:
for col in range(x1, x2):
row = y1
self.H_routing_cong[row * self.grid_col + col] += weight
......@@ -1261,12 +1313,8 @@ class PlacementCost(object):
for row in range(min(y2, y1), max(y2, y1)):
col = x2
self.V_routing_cong[row * self.grid_col + col] += weight
return
# print('sk4')
self.t_routing(temp_gcell, weight)
return
else:
self.__t_routing(temp_gcell, weight)
def __macro_route_over_grid_cell(self, mod_x, mod_y, mod_w, mod_h):
"""
......@@ -1363,6 +1411,9 @@ class PlacementCost(object):
self.H_macro_routing_cong[r_i * self.grid_col + c_i] -= y_dist * self.hrouting_alloc
def __split_net(self, source_gcell, node_gcells):
"""
private function: Split >3 pin net into multiple two-pin nets
"""
splitted_netlist = []
for node_gcell in node_gcells:
if node_gcell != source_gcell:
......@@ -1370,14 +1421,18 @@ class PlacementCost(object):
return splitted_netlist
def get_vertical_routing_congestion(self):
# TODO: detect if we need to run
"""
Return Vertical Routing Congestion
"""
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
"""
Return Horizontal Routing Congestion
"""
if self.FLAG_UPDATE_CONGESTION:
self.get_routing()
......@@ -1385,7 +1440,7 @@ class PlacementCost(object):
def get_routing(self):
"""
Route between modules
H/V Routing Before Computing Routing Congestions
"""
if self.FLAG_UPDATE_CONGESTION:
self.grid_width = float(self.width/self.grid_col)
......@@ -1458,8 +1513,6 @@ class PlacementCost(object):
for curr_net in self.__split_net(source_gcell=source_gcell, node_gcells=node_gcells):
self.__two_pin_net_routing(source_gcell=source_gcell, node_gcells=curr_net, weight=weight)
# print("V_routing_cong", self.V_routing_cong)
# print("H_routing_cong", self.H_routing_cong)
# normalize routing congestion
for idx, v_gcell in enumerate(self.V_routing_cong):
self.V_routing_cong[idx] = float(v_gcell / self.grid_v_routes)
......@@ -1480,6 +1533,9 @@ class PlacementCost(object):
self.H_routing_cong = [sum(x) for x in zip(self.H_routing_cong, self.H_macro_routing_cong)]
def __smooth_routing_cong(self):
"""
Smoothing V/H Routing congestion
"""
temp_V_routing_cong = [0] * self.grid_col * self.grid_row
temp_H_routing_cong = [0] * self.grid_col * self.grid_row
......@@ -1525,74 +1581,63 @@ class PlacementCost(object):
def is_node_soft_macro(self, node_idx) -> bool:
"""
Return None or return ref_id
Return if node is a soft macro
"""
try:
return node_idx in self.soft_macro_indices
except IndexError:
print("[ERROR INDEX OUT OF RANGE] Can not process index at {}".format(node_idx))
exit(0)
exit(1)
def is_node_hard_macro(self, node_idx) -> bool:
"""
Return None or return ref_id
Return if node is a hard macro
"""
try:
return node_idx in self.hard_macro_indices
except IndexError:
print("[ERROR INDEX OUT OF RANGE] Can not process index at {}".format(node_idx))
exit(0)
exit(1)
def get_node_name(self, node_idx: int) -> str:
return self.indices_to_mod_name[node_idx]
def _get_node_mask(self, node_idx: int, node_name: str=None) -> list:
"""
Return Grid_col x Grid_row:
1 == placable
0 == unplacable
Placement Constraint:
- center @ grid cell
- no overlapping other macro
- no OOB
Return node name based on given node index
"""
if self.FLAG_UPDATE_NODE_MASK:
self.__update_node_mask()
module = self.modules_w_pins[node_idx]
temp_node_mask = np.array([0] * (self.grid_col * self.grid_row))\
.reshape(self.grid_row, self.grid_col)
if module.get_placed_flag():
pass
else:
hor_pad, ver_pad = self.__node_pad_cell(mod_width=module.get_width(),
mod_height=module.get_height())
# row, along y-axis, height
for i in range(ver_pad, self.grid_row - ver_pad):
for j in range(hor_pad, self.grid_col - hor_pad):
cell_region = self.node_mask[i - ver_pad : i + ver_pad + 1,
j - hor_pad : j + hor_pad + 1]
if (cell_region == 1).all():
temp_node_mask[i][j] = 1
return temp_node_mask.flatten()
try:
return self.indices_to_mod_name[node_idx]
except Exception:
print("[ERROR NODE INDEX] Node not found!")
exit(1)
def get_node_index(self, node_name: str) -> int:
"""
Return node index based on given node name
"""
try:
return self.mod_name_to_indices[node_name]
except Exception:
print("[ERROR NODE NAME] Node not found!")
exit(1)
def get_node_mask(self, node_idx: int, node_name: str=None) -> list:
"""
Return node mask based on given node
All legal positions must satisfy:
- No Out-of-Bound
- No Overlapping with previously placed MACROs
"""
mod = self.modules_w_pins[node_idx]
canvas_block = Block(x_max=self.width,
y_max=self.height,
x_min=0,
y_min=0)
mod_w = mod.get_width()
mod_h = mod.get_height()
if mod.get_type() == "PORT" or mod.get_type() == "MACRO_PIN":
mod_w = 1e-3
mod_h = 1e-3
else:
mod_w = mod.get_width()
mod_h = mod.get_height()
temp_node_mask = np.array([1] * (self.grid_col * self.grid_row))\
.reshape(self.grid_row, self.grid_col)
......@@ -1600,15 +1645,10 @@ class PlacementCost(object):
self.grid_width = float(self.width/self.grid_col)
self.grid_height = float(self.height/self.grid_row)
# print(self.grid_col, self.grid_row)
# print(mod_w*mod_h)
for i in range(self.grid_row):
for j in range(self.grid_col):
# try every location
# construct block based on current module
# construct block based on current module dimenstion
temp_x = j * self.grid_width + (self.grid_width/2)
temp_y = i * self.grid_height + (self.grid_height/2)
......@@ -1621,10 +1661,9 @@ class PlacementCost(object):
# check OOB
if abs(self.__overlap_area(
block_i=canvas_block, block_j=mod_block) - (mod_w*mod_h)) > 1e-8:
# print(i, j, self.__overlap_area(
# block_i=canvas_block, block_j=mod_block))
temp_node_mask[i][j] = 0
else:
# check overlapping
for pmod_idx in self.placed_macro:
pmod = self.modules_w_pins[pmod_idx]
if not pmod.get_placed_flag():
......@@ -1634,12 +1673,12 @@ class PlacementCost(object):
p_w = pmod.get_width()
p_h = pmod.get_height()
pmod_block = Block(
x_max=p_x + (p_w/2) + 1,
y_max=p_y + (p_h/2) + 1,
x_min=p_x - (p_w/2) - 1,
y_min=p_y - (p_h/2) - 1
x_max=p_x + (p_w/2),
y_max=p_y + (p_h/2),
x_min=p_x - (p_w/2),
y_min=p_y - (p_h/2)
)
# overlap with placed module
# if overlap with placed module
if self.__overlap_area(block_i=pmod_block, block_j=mod_block) > 0:
temp_node_mask[i][j] = 0
......@@ -1654,7 +1693,7 @@ class PlacementCost(object):
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))
print("[WARNING INDEX OUT OF RANGE] Can not process index at {}".format(node_idx))
return None
......@@ -1665,7 +1704,7 @@ class PlacementCost(object):
mod = None
try:
mod = self.modules_w_pins[node_idx]
assert mod.get_type() in ['MACRO', 'macro', 'STDCELL', 'PORT']
assert mod.get_type() in ['MACRO', 'STDCELL', 'PORT']
except AssertionError:
print("[ERROR NODE FIXED] Found {}. Only 'MACRO', 'macro', 'STDCELL'".format(mod.get_type())
+"'PORT' are considered to be fixable nodes")
......@@ -1677,12 +1716,40 @@ class PlacementCost(object):
return mod.get_width(), mod.get_height()
def make_soft_macros_square(self):
pass
"""
[IGNORE] THIS DOES NOT AFFECT DENSITY. SHOULD WE IMPLEMENT THIS AT ALL?
make soft macros as squares
"""
return
for mod_idx in self.soft_macro_indices:
mod = self.modules_w_pins[mod_idx]
mod_area = mod.get_width() * mod.get_height()
mod.set_width(math.sqrt(mod_area))
mod.set_height(math.sqrt(mod_area))
def update_soft_macros_position(self, coord_dict):
"""
For sync-up with Google's plc_client after FD placer
"""
for mod_idx in coord_dict.keys():
self.modules_w_pins[mod_idx].set_pos(coord_dict[mod_idx])
def set_soft_macro_position(self, node_idx, x_pos, y_pos):
"""
used for updating soft macro position
"""
self.modules_w_pins[node_idx].set_pos(x_pos, y_pos)
def set_use_incremental_cost(self, use_incremental_cost):
"""
NOT IMPLEMENTED
"""
self.use_incremental_cost = use_incremental_cost
def get_use_incremental_cost(self):
"""
NOT IMPLEMENTED
"""
return self.use_incremental_cost
def get_macro_adjacency(self) -> list:
......@@ -1791,8 +1858,6 @@ class PlacementCost(object):
# instantiate clustered ports
for row_idx, cluster_cell in enumerate(sorted(clustered_ports, key=lambda tup: tup[1])):
# print("cluster_cell", cluster_cell)
# print("port cnt", len(clustered_ports[cluster_cell]))
# add cell location
cell_location[row_idx] = cluster_cell[0] * self.grid_col + cluster_cell[1]
......@@ -1803,7 +1868,6 @@ class PlacementCost(object):
for curr_port in clustered_ports[cluster_cell]:
# get module name
curr_port_name = curr_port.get_name()
# print("curr_port_name", curr_port_name, curr_port.get_pos())
# assuming ports only connects to macros
for col_idx, h_module_idx in enumerate(module_indices):
# col index
......@@ -1812,8 +1876,6 @@ class PlacementCost(object):
h_module = self.modules_w_pins[h_module_idx]
# get connected module name
h_module_name = h_module.get_name()
# print("other connections", h_module.get_connection(), curr_port_name)
if curr_port_name in h_module.get_connection():
entry += h_module.get_connection()[curr_port_name]
......@@ -2084,13 +2146,21 @@ class PlacementCost(object):
exit(1)
if not mod.get_fix_flag():
mod.set_placed_flag(True)
self.placed_macro.remove(node_idx)
# update flag
self.FLAG_UPDATE_CONGESTION = True
self.FLAG_UPDATE_DENSITY = True
# self.FLAG_UPDATE_NODE_MASK = True # placeholder
self.FLAG_UPDATE_WIRELENGTH = True
if node_idx in self.hard_macro_indices:
mod.set_placed_flag(False)
self.placed_macro.remove(node_idx)
# update flag
self.FLAG_UPDATE_CONGESTION = True
self.FLAG_UPDATE_DENSITY = True
# self.FLAG_UPDATE_NODE_MASK = True # placeholder
self.FLAG_UPDATE_WIRELENGTH = True
elif node_idx in self.soft_macro_indices:
mod.set_placed_flag(False)
# update flag
self.FLAG_UPDATE_CONGESTION = True
self.FLAG_UPDATE_DENSITY = True
# self.FLAG_UPDATE_NODE_MASK = True # placeholder
self.FLAG_UPDATE_WIRELENGTH = True
else:
print("[WARNING UNPLACE NODE] Trying to unplace a fixed node")
......@@ -2135,7 +2205,8 @@ class PlacementCost(object):
pass
def get_source_filename(self):
"""return netlist path
"""
return netlist path
"""
return self.netlist_file
......@@ -2146,20 +2217,18 @@ class PlacementCost(object):
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.
"""
ref_node_id is used for macro_pins. Refers to the macro it belongs to.
"""
if node_idx != -1:
if node_idx in self.soft_macro_pin_indices:
pin = self.modules_w_pins[node_idx]
return self.mod_name_to_indices[pin.get_macro_name()]
elif node_idx in self.hard_macro_pin_indices:
if node_idx in self.soft_macro_pin_indices or node_idx in self.hard_macro_pin_indices:
pin = self.modules_w_pins[node_idx]
return self.mod_name_to_indices[pin.get_macro_name()]
return -1
def save_placement(self, filename, info):
def save_placement(self, filename, info=""):
"""
When writing out info line-by-line, add a "#" at front
When writing out info line-by-line, add a "#" at front
"""
with open(filename, 'w+') as f:
for line in info.split('\n'):
......@@ -2187,6 +2256,9 @@ class PlacementCost(object):
def display_canvas( self,
annotate=True,
amplify=False):
"""
Non-google function, For quick canvas view
"""
#define Matplotlib figure and axis
fig, ax = plt.subplots(figsize=(8,8), dpi=50)
......@@ -2407,6 +2479,12 @@ class PlacementCost(object):
def get_width(self):
return self.width
def set_height(self, height):
self.height = height
def set_width(self, width):
self.width = width
def set_location(self, grid_cell_idx):
self.location = grid_cell_idx
......@@ -2671,7 +2749,7 @@ class PlacementCost(object):
def main():
test_netlist_dir = './Plc_client/test/'+\
'ariane133'
'ariane_68_1.3'
netlist_file = os.path.join(test_netlist_dir,
'netlist.pb.txt')
plc = PlacementCost(netlist_file)
......
from ast import Assert
import numpy as np
import pandas as pd
import sys
......@@ -44,20 +45,19 @@ Example:
--marv 51.790\
--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\
$ python3 -m Plc_client.plc_client_os_test --netlist ./Plc_client/test/ariane_68_1.3/netlist.pb.txt\
--plc ./Plc_client/test/ariane_68_1.3/legalized.plc\
--width 1347.100\
--height 1346.800\
--col 23\
--row 28\
--rpmh 11.285\
--rpmv 12.605\
--marh 7.143\
--marv 8.339\
--smooth 2
$ python3 -m Plc_client.plc_client_os_test --netlist ./Plc_client/test/0P2M0m/netlist.pb.txt\
--plc ./Plc_client/test/0P2M0m/initial.plc\
--width 500\
--height 500\
--col 5\
......@@ -138,7 +138,7 @@ class PlacementCostTest():
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")
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,
......@@ -147,7 +147,7 @@ class PlacementCostTest():
self.plc.set_canvas_boundary_check(False)
self.plc.restore_placement(self.PLC_PATH)
else:
print("[PLC FILE MISSING] Using only netlist info")
print("#[PLC FILE MISSING] Using only netlist info")
try:
assert int(self.plc_os.get_area()) == int(self.plc.get_area())
......@@ -223,7 +223,7 @@ class PlacementCostTest():
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")
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=ifInital,
......@@ -246,7 +246,7 @@ class PlacementCostTest():
print("overlap_threshold default", self.plc.get_overlap_threshold())
if self.PLC_PATH:
print("[PLC FILE FOUND] Loading info from .plc file")
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,
......@@ -255,7 +255,7 @@ class PlacementCostTest():
self.plc.set_canvas_boundary_check(False)
self.plc.restore_placement(self.PLC_PATH)
else:
print("[PLC FILE MISSING] Using only netlist info")
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)
......@@ -271,7 +271,11 @@ class PlacementCostTest():
self.plc_os.set_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT)
self.plc_os.set_placement_grid(self.GRID_COL, self.GRID_ROW)
# TODO: [IGNORE] create_blockage must be defined BEFORE set_canvas_size and set_placement_grid in order to be considered on the canvas
self.plc.make_soft_macros_square()
self.plc_os.make_soft_macros_square()
# [IGNORE] create_blockage must be defined BEFORE set_canvas_size
# and set_placement_grid in order to be considered on the canvas
if False:
self.plc.create_blockage(0.0, 100.0, 300.0, 300.0, 1.0)
self.plc.create_blockage(300, 0, 500, 200, 1)
......@@ -280,16 +284,19 @@ class PlacementCostTest():
print(self.plc.set_use_incremental_cost(True))
print(self.plc_os.get_soft_macros_count())
# self.plc_os.display_canvas(annotate=False)
# 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
print("#[INFO WIRELENGTH] Matched irelength cost -- GL {}, OS {}".format(
assert int(self.plc_os.get_wirelength()) == int(self.plc.get_wirelength())
assert abs(self.plc.get_cost() - self.plc_os.get_cost()) <= 1e-2
print("#[INFO WIRELENGTH] Matched Wirelength cost -- GL {}, OS {}".format(
str(self.plc.get_cost()), self.plc_os.get_cost()))
except Exception as e:
print("[ERROR WIRELENGTH] Discrepancies found when computing wirelength -- GL {}, OS {}".format(
str(self.plc.get_cost()), self.plc_os.get_cost()))
print("GL WIRELENGTH: ", self.plc.get_wirelength())
print("OS WIRELENGTH: ", self.plc_os.get_wirelength())
exit(1)
# Density
......@@ -307,10 +314,10 @@ class PlacementCostTest():
# Congestion
try:
# NOTE: [IGNORE] grid-wise congestion not tested because
# NOTE: [IGNORE] grid-wise congestion not tested because
# miscellaneous implementation differences.
assert abs(self.plc.get_congestion_cost() -
self.plc_os.get_congestion_cost()) < 1e-3
self.plc_os.get_congestion_cost()) <= 1e-2
print("#[INFO CONGESTION] Matched congestion cost -- GL {}, OS {}".format(
str(self.plc.get_congestion_cost()), self.plc_os.get_congestion_cost()))
except Exception as e:
......@@ -324,6 +331,7 @@ class PlacementCostTest():
print(" +++++++++++++++++++++++++++++")
def test_miscellaneous(self):
print("****************** miscellaneous ******************")
# Google's Binary Executable
self.plc = plc_client.PlacementCost(self.NETLIST_PATH)
self.plc_os = plc_client_os.PlacementCost(netlist_file=self.NETLIST_PATH,
......@@ -342,11 +350,10 @@ class PlacementCostTest():
self.plc_os.set_placement_grid(self.GRID_COL, self.GRID_ROW)
self.plc_os.display_canvas()
self.unplace_node()
self.plc_os.unplace_all_nodes()
print(np.flip(np.array(self.plc_util.get_node_mask(0)).reshape(35, 33), axis=0))
print(np.flip(np.array(self.plc.get_node_mask(0)).reshape(35, 33), axis=0))
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)
......@@ -366,6 +373,127 @@ class PlacementCostTest():
# print("can_place_node", self.plc.can_place_node(0, 1))
print("***************************************************")
def test_proxy_hpwl(self):
print("############################ TEST PROXY WIRELENGTH ############################")
# 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.get_overlap_threshold()
print("overlap_threshold default", self.plc.get_overlap_threshold())
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)
# 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-2
print("#[INFO WIRELENGTH] Matched Wirelength cost -- GL {}, OS {}".format(
str(self.plc.get_cost()), self.plc_os.get_cost()))
except Exception as e:
print("[ERROR WIRELENGTH] Discrepancies found when computing wirelength -- GL {}, OS {}".format(
str(self.plc.get_cost()), self.plc_os.get_cost()))
# if remove all soft macros
# soft_macro_indices = [
# m for m in self.plc.get_macro_indices() if self.plc.is_node_soft_macro(m)
# ]
# for mod_idx in soft_macro_indices:
# self.plc_os.unplace_node(mod_idx)
# self.plc.unplace_node(mod_idx)
print("GL WIRELENGTH: ", self.plc.get_wirelength())
print("OS WIRELENGTH: ", self.plc_os.get_wirelength())
def test_proxy_density(self):
print("############################ TEST PROXY DENSITY ############################")
# 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.get_overlap_threshold()
print("overlap_threshold default", self.plc.get_overlap_threshold())
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)
# self.plc.make_soft_macros_square()
# self.plc_os.make_soft_macros_square()
# 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())
print("#[INFO DENSITY] Matched density cost -- GL {}, OS {}".format(
str(self.plc.get_density_cost()), self.plc_os.get_density_cost()))
except Exception as e:
print("[ERROR DENSITY] Discrepancies found when computing density -- GL {}, OS {}".format(
str(self.plc.get_density_cost()), self.plc_os.get_density_cost()))
gl_density = self.plc.get_grid_cells_density()
os_density = self.plc_os.get_grid_cells_density()
for cell_idx, (gl_dens, os_des) in enumerate(zip(gl_density, os_density)):
print("PASS {}".format(str(cell_idx)) if abs(gl_dens - os_des) <= 1e-3 else "FAILED", gl_dens, os_des)
if cell_idx == 0:
break
self.plc_os.display_canvas(annotate=True, amplify=True)
def test_proxy_congestion(self):
# Google's API
self.plc = plc_client.PlacementCost(self.NETLIST_PATH)
......@@ -387,7 +515,7 @@ class PlacementCostTest():
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")
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,
......@@ -396,7 +524,7 @@ class PlacementCostTest():
self.plc.set_canvas_boundary_check(False)
self.plc.restore_placement(self.PLC_PATH)
else:
print("[PLC FILE MISSING] Using only netlist info")
print("#[PLC FILE MISSING] Using only netlist info")
temp_gl_h = np.array(self.plc.get_horizontal_routing_congestion())
temp_os_h = np.array(self.plc_os.get_horizontal_routing_congestion())
......@@ -461,7 +589,7 @@ class PlacementCostTest():
CELL_IDX = 0
r = (CELL_IDX // 35)
c = int(CELL_IDX % 35)
print(r,c)
print(r, c)
print(temp_gl_h_rt[CELL_IDX], temp_os_h_rt[CELL_IDX])
print(temp_gl_v_rt[CELL_IDX], temp_os_v_rt[CELL_IDX])
......@@ -475,6 +603,7 @@ class PlacementCostTest():
assert self.PLC_PATH
except AssertionError:
print("[ERROR PLACEMENT UTIL TEST] Facilitate required .plc file")
exit(1)
self.plc_util = placement_util.create_placement_cost(
plc_client=plc_client,
......@@ -537,6 +666,7 @@ class PlacementCostTest():
except AssertionError:
print("[ERROR PLACEMENT UTIL] Saved PLC Discrepency found at line {}".format(
str(idx)))
exit(1)
# if keep plc file for detailed comparison
if not keep_save_file:
......@@ -563,13 +693,14 @@ class PlacementCostTest():
self.extractor = observation_extractor.ObservationExtractor(
plc=plc, observation_config=self._observation_config)
"""
print("############################ TEST OBSERVATION EXTRACTOR ############################")
try:
assert self.PLC_PATH
except AssertionError:
print("[ERROR OBSERVATION EXTRACTOR TEST] Facilitate required .plc file")
exit(1)
# Using the default edge/node
self._observation_config = observation_config.ObservationConfig(
......@@ -581,27 +712,23 @@ class PlacementCostTest():
init_placement=self.PLC_PATH
)
self.plc_util.unplace_all_nodes()
self.plc_util_os = placement_util.create_placement_cost(
plc_client=plc_client_os,
netlist_file=self.NETLIST_PATH,
init_placement=self.PLC_PATH
)
self.plc_util_os.unplace_all_nodes()
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)
print("#[PLC FILE FOUND] Loading info from .plc file")
self.plc_util_os.set_canvas_boundary_check(False)
self.plc_util_os.restore_placement(self.PLC_PATH,
ifInital=True,
ifValidate=True,
ifReadComment=False)
self.plc_util.set_canvas_boundary_check(False)
self.plc_util.restore_placement(self.PLC_PATH)
else:
print("[PLC FILE MISSING] Using only netlist info")
print("#[PLC FILE MISSING] Using only netlist info")
self.extractor = observation_extractor.ObservationExtractor(
plc=self.plc_util, observation_config=self._observation_config
......@@ -610,13 +737,27 @@ class PlacementCostTest():
self.extractor_os = observation_extractor.ObservationExtractor(
plc=self.plc_util_os, observation_config=self._observation_config
)
# Unplacing all the nodes b/c by default everything is placed on board at initialization
self.plc_util_os.unplace_all_nodes()
self.plc_util.unplace_all_nodes()
# Static features that are invariant across training steps
static_feature_gl = self.extractor._extract_static_features()
static_feature_os = self.extractor_os._extract_static_features()
#
for feature_gl, feature_os in zip(static_feature_gl, static_feature_os):
assert (static_feature_gl[feature_gl] ==
static_feature_os[feature_os]).all()
try:
assert (static_feature_gl[feature_gl] ==
static_feature_os[feature_os]).all()
except AssertionError:
print(
"[ERROR OBSERVATION EXTRACTOR TEST] Observation Feature Mismatch on "+str(feature_gl))
print("GL FEATURE:")
print(static_feature_gl[feature_gl])
print("OS FEATURE:")
print(static_feature_os[feature_os])
exit(1)
print(" ++++++++++++++++++++++++++++++++++++++++")
print(" +++ TEST OBSERVATION EXTRACTOR: PASS +++")
......@@ -650,22 +791,36 @@ class PlacementCostTest():
self.plc_util_os.set_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT)
self.plc_util_os.set_placement_grid(self.GRID_COL, self.GRID_ROW)
# MACRO placement order
ordered_node_gl = placement_util.get_ordered_node_indices(
mode='descending_size_macro_first', plc=self.plc_util)
ordered_node_os = placement_util.get_ordered_node_indices(
mode='descending_size_macro_first', plc=self.plc_util_os)
assert (np.array(ordered_node_gl) == np.array(ordered_node_os)).all()
try:
assert (np.array(ordered_node_gl) == np.array(ordered_node_os)).all()
except:
print("[ERROR PLACE NODE] Node Ordering not matching!")
# Initialize Placement
self.plc_util_os.unplace_all_nodes()
self.plc_util.unplace_all_nodes()
NODE_TO_PLACE_IDX = 0
self.plc_util_os.get_macro_indices()
self._hard_macro_indices = [
m for m in self.plc_util_os.get_macro_indices()
if not self.plc_util_os.is_node_soft_macro(m)
]
NODE_TO_PLACE_IDX = self._hard_macro_indices[0]
# make sure this is within grid cell range
CELL_TO_PLACE_IDX = 6
print("MASK FOR PLACING FIRST NODE:")
print("[INFO PLACE NODE] MASK FOR PLACING FIRST NODE:")
self.plc_util_os.display_canvas(annotate=False)
print("OS NODE MASK:")
print(np.flip(np.array(self.plc_util_os.get_node_mask(
NODE_TO_PLACE_IDX)).reshape(self.GRID_ROW, self.GRID_COL), axis=0))
print("GL NODE MASK:")
print(np.flip(np.array(self.plc_util.get_node_mask(NODE_TO_PLACE_IDX)).reshape(
self.GRID_ROW, self.GRID_COL), axis=0))
......@@ -673,11 +828,14 @@ class PlacementCostTest():
self.plc_util.place_node(NODE_TO_PLACE_IDX, CELL_TO_PLACE_IDX)
# place node NODE_TO_PLACE_IDX @ position CELL_TO_PLACE_IDX
NODE_TO_PLACE_IDX = 1
NODE_TO_PLACE_IDX = self._hard_macro_indices[1]
# make sure this is within grid cell range
CELL_TO_PLACE_IDX = 18
print("MASK FOR PLACING SECOND NODE:")
print("[INFO PLACE NODE] MASK FOR PLACING SECOND NODE:")
print("OS NODE MASK:")
print(np.flip(np.array(self.plc_util_os.get_node_mask(
NODE_TO_PLACE_IDX)).reshape(self.GRID_ROW, self.GRID_COL), axis=0))
print("GL NODE MASK:")
print(np.flip(np.array(self.plc_util.get_node_mask(NODE_TO_PLACE_IDX)).reshape(
self.GRID_ROW, self.GRID_COL), axis=0))
self.plc_util_os.place_node(NODE_TO_PLACE_IDX, CELL_TO_PLACE_IDX)
......@@ -687,52 +845,69 @@ class PlacementCostTest():
def test_environment(self):
print("############################ TEST ENVIRONMENT ############################")
# Source: https://github.com/google-research/circuit_training/blob/d5e454e5bcd153a95d320f664af0d1b378aace7b/circuit_training/environment/environment_test.py#L39
def random_action(mask):
valid_actions, = np.nonzero(mask.flatten())
if len(valid_actions): # pylint: disable=g-explicit-length-test
return np.random.choice(valid_actions)
# If there is no valid choice, then `[0]` is returned which results in an
# infeasable action ending the episode.
return 0
env = environment.CircuitEnv(
_plc=plc_client,
create_placement_cost_fn=placement_util.create_placement_cost,
netlist_file=self.NETLIST_PATH,
init_placement=self.PLC_PATH)
self.plc_util = placement_util.create_placement_cost(
plc_client=plc_client,
netlist_file=self.NETLIST_PATH,
init_placement=self.PLC_PATH
)
# print(np.array2string(env._current_mask.reshape(128, 128)), sep=']')
init_placement=self.PLC_PATH,
unplace_all_nodes_in_init=False)
env_os = environment.CircuitEnv(
_plc=plc_client_os,
create_placement_cost_fn=placement_util.create_placement_cost,
netlist_file=self.NETLIST_PATH,
init_placement=self.PLC_PATH)
# print(np.array(env_os._plc.get_node_mask(13333)).reshape(33,35))
# print(np.array(env._plc.get_node_mask(13333)).reshape(33,35))
assert (env_os._get_mask() == env._get_mask()).all()
init_placement=self.PLC_PATH,
unplace_all_nodes_in_init=False)
# Init Mask
try:
assert (env_os._get_mask() == env._get_mask()).all()
except AssertionError:
print("[ERROR ENVIRONMENT TEST] Init Mask failed")
print("GL INIT MASK:")
node_idx = env._sorted_node_indices[0]
print(np.flip(np.array(env._plc.get_node_mask(node_idx)).reshape(
env._plc.get_grid_num_columns_rows()), axis=0))
print("OS INIT MASK:")
print(np.flip(np.array(env_os._plc.get_node_mask(node_idx)).reshape(
env_os._plc.get_grid_num_columns_rows()), axis=0))
# check if node information is matching
for idx in env._plc.get_macro_indices():
try:
assert (env._plc.get_node_location(idx) != env_os._plc.get_node_location(idx))
except AssertionError:
print("[ERROR ENVIRONMENT TEST] Node location not matching!")
print(idx, env._plc.get_node_location(idx), env_os._plc.get_node_location(idx))
exit(1)
try:
assert abs(env._plc.get_node_width_height(idx)[0] - env_os._plc.get_node_width_height(idx)[0]) >= 1e-3 and \
abs(env._plc.get_node_width_height(idx)[1] - env_os._plc.get_node_width_height(idx)[1]) >= 1e-3
except AssertionError:
print("[ERROR ENVIRONMENT TEST] Node dimension not matching!")
print(idx, env._plc.get_node_width_height(idx), env_os._plc.get_node_width_height(idx))
exit(1)
env_os._plc.display_canvas(annotate=False)
# TODO DISCREPENCY FOUND
exit(1)
# check observation state
obs_gl = env._get_obs()
obs_os = env_os._get_obs()
env_os.reset()
env.reset()
# env_os.reset()
# env.reset()
for feature_gl, feature_os in zip(obs_gl, obs_os):
if not (obs_gl[feature_gl] == obs_os[feature_os]).all():
print(feature_gl, feature_os)
try:
assert (obs_gl[feature_gl] == obs_os[feature_os]).all()
except AssertionError:
print("[ERROR ENVIRONMENT TEST] Failing on "+str(feature_gl))
print(np.where(obs_gl[feature_gl] != obs_os[feature_os]))
exit(1)
print(" ++++++++++++++++++++++++++++++")
print(" +++ TEST ENVIRONMENT: PASS +++")
print(" ++++++++++++++++++++++++++++++")
......@@ -791,14 +966,18 @@ def main(args):
marv=args.marv,
smooth=args.smooth)
"""
Uncomment any available tests
"""
# PCT.test_metadata()
PCT.test_proxy_cost()
# PCT.test_proxy_density()
# PCT.test_proxy_congestion()
# PCT.test_placement_util(keep_save_file=False)
# PCT.test_place_node()
# PCT.test_miscellaneous()
# PCT.test_observation_extractor()
# PCT.view_canvas()
# PCT.test_proxy_congestion()
# PCT.test_environment()
......
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