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 ...@@ -25,6 +25,18 @@ python -m Plc_client.plc_client_os_test --netlist ./Plc_client/test/ariane/netli
--smooth 2 --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 ## HPWL Computation
Given a net $i$, its wirelength can be computed as the following: Given a net $i$, its wirelength can be computed as the following:
...@@ -85,11 +97,8 @@ $$ ...@@ -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. 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
**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)**. **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)**.
## 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)**.
# [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))
...@@ -203,15 +203,12 @@ class ObservationExtractor(object): ...@@ -203,15 +203,12 @@ class ObservationExtractor(object):
def _get_clustered_port_locations( def _get_clustered_port_locations(
self, grid_cell_index: int) -> Tuple[float, float]: self, grid_cell_index: int) -> Tuple[float, float]:
"""Returns clustered port locations. """Returns clustered port locations.
This function returns an approximation location of the ports in a grid This function returns an approximation location of the ports in a grid
cell. Depending on the cell location in the canvas, the approximation cell. Depending on the cell location in the canvas, the approximation
differs. differs.
Args: Args:
grid_cell_index: The index of the grid cell where the cluster port is grid_cell_index: The index of the grid cell where the cluster port is
located. located.
Returns: Returns:
A tuple of float: Approximate x, y location of the port cluster in the 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). grid cell in the same unit as canvas width and height (micron).
......
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
"""A collection of non-prod utility functions for placement. """A collection of non-prod utility functions for placement.
All the dependencies in this files should be non-prod. All the dependencies in this files should be non-prod.
""" """
...@@ -80,11 +79,9 @@ def restore_macro_orientations(plc: plc_client.PlacementCost, ...@@ -80,11 +79,9 @@ def restore_macro_orientations(plc: plc_client.PlacementCost,
def extract_attribute_from_comments(attribute: str, def extract_attribute_from_comments(attribute: str,
filenames: List[str]) -> Optional[str]: filenames: List[str]) -> Optional[str]:
"""Parses the files' comments section, tries to extract the attribute. """Parses the files' comments section, tries to extract the attribute.
Args: Args:
attribute: attribute to look for (case sensetive). attribute: attribute to look for (case sensetive).
filenames: List of protobuf file or a plc file. filenames: List of protobuf file or a plc file.
Returns: Returns:
Attribute name string, or None if not found. Attribute name string, or None if not found.
""" """
...@@ -134,10 +131,8 @@ def get_blockages_from_comments( ...@@ -134,10 +131,8 @@ def get_blockages_from_comments(
def extract_sizes_from_comments( def extract_sizes_from_comments(
filenames: List[str]) -> Optional[Tuple[float, float, int, int]]: filenames: List[str]) -> Optional[Tuple[float, float, int, int]]:
"""Parses the file's comments section, tries to extract canvas/grid sizes. """Parses the file's comments section, tries to extract canvas/grid sizes.
Args: Args:
filenames: A list of netlist (.pb.txt) or placement (.plc) files. filenames: A list of netlist (.pb.txt) or placement (.plc) files.
Returns: Returns:
Tuple of canvas_width, canvas_height, grid_cols, grid_rows Tuple of canvas_width, canvas_height, grid_cols, grid_rows
""" """
...@@ -174,7 +169,6 @@ def extract_sizes_from_comments( ...@@ -174,7 +169,6 @@ def extract_sizes_from_comments(
# done # done
def fix_port_coordinates(plc: plc_client.PlacementCost) -> None: def fix_port_coordinates(plc: plc_client.PlacementCost) -> None:
"""Find all ports and fix their coordinates. """Find all ports and fix their coordinates.
Args: Args:
plc: the placement cost object. plc: the placement cost object.
""" """
...@@ -204,7 +198,6 @@ def create_placement_cost( ...@@ -204,7 +198,6 @@ def create_placement_cost(
macro_vertical_routing_allocation: float = 51.79, macro_vertical_routing_allocation: float = 51.79,
) -> plc_client.PlacementCost: ) -> plc_client.PlacementCost:
"""Creates a placement_cost object. """Creates a placement_cost object.
Args: Args:
netlist_file: Path to the netlist proto text file. netlist_file: Path to the netlist proto text file.
init_placement: Path to the inital placement .plc file. init_placement: Path to the inital placement .plc file.
...@@ -218,7 +211,6 @@ def create_placement_cost( ...@@ -218,7 +211,6 @@ def create_placement_cost(
vertical_routes_per_micron: Vertical route capacity per micros. vertical_routes_per_micron: Vertical route capacity per micros.
macro_horizontal_routing_allocation: Macro horizontal routing allocation. macro_horizontal_routing_allocation: Macro horizontal routing allocation.
macro_vertical_routing_allocation: Macro vertical routing allocation. macro_vertical_routing_allocation: Macro vertical routing allocation.
Returns: Returns:
A PlacementCost object. A PlacementCost object.
""" """
...@@ -272,10 +264,8 @@ def create_placement_cost( ...@@ -272,10 +264,8 @@ def create_placement_cost(
def get_node_type_counts(plc: plc_client.PlacementCost) -> Dict[str, int]: def get_node_type_counts(plc: plc_client.PlacementCost) -> Dict[str, int]:
"""Returns number of each type of nodes in the netlist. """Returns number of each type of nodes in the netlist.
Args: Args:
plc: the placement cost object. plc: the placement cost object.
Returns: Returns:
Number of each type of node in a dict. Number of each type of node in a dict.
""" """
...@@ -391,7 +381,6 @@ def fd_placement_schedule(plc: plc_client.PlacementCost, ...@@ -391,7 +381,6 @@ def fd_placement_schedule(plc: plc_client.PlacementCost,
use_current_loc: bool = False, use_current_loc: bool = False,
move_macros: bool = False) -> None: move_macros: bool = False) -> None:
"""A placement schedule that uses force directed method. """A placement schedule that uses force directed method.
Args: Args:
plc: The plc object. plc: The plc object.
num_steps: Number of steps of the force-directed algorithm during each call. num_steps: Number of steps of the force-directed algorithm during each call.
...@@ -425,12 +414,10 @@ def get_ordered_node_indices(mode: str, ...@@ -425,12 +414,10 @@ def get_ordered_node_indices(mode: str,
plc: plc_client.PlacementCost, plc: plc_client.PlacementCost,
exclude_fixed_nodes: bool = True) -> List[int]: exclude_fixed_nodes: bool = True) -> List[int]:
"""Returns an ordering of node indices according to the specified mode. """Returns an ordering of node indices according to the specified mode.
Args: Args:
mode: node ordering mode mode: node ordering mode
plc: placement cost object plc: placement cost object
exclude_fixed_nodes: Whether fixed nodes should be excluded. exclude_fixed_nodes: Whether fixed nodes should be excluded.
Returns: Returns:
Node indices sorted according to the mode. Node indices sorted according to the mode.
""" """
...@@ -465,10 +452,8 @@ def get_ordered_node_indices(mode: str, ...@@ -465,10 +452,8 @@ def get_ordered_node_indices(mode: str,
def extract_parameters_from_comments( def extract_parameters_from_comments(
filename: str) -> Tuple[float, float, int, int]: filename: str) -> Tuple[float, float, int, int]:
"""Parses the file's comments section, tries to extract canvas/grid sizes. """Parses the file's comments section, tries to extract canvas/grid sizes.
Args: Args:
filename: protobuf file or a plc file. filename: protobuf file or a plc file.
Returns: Returns:
Tuple of canvas_width, canvas_height, grid_cols, grid_rows Tuple of canvas_width, canvas_height, grid_cols, grid_rows
""" """
...@@ -503,9 +488,7 @@ def extract_parameters_from_comments( ...@@ -503,9 +488,7 @@ def extract_parameters_from_comments(
# done # done
def get_routing_resources() -> Dict[str, float]: def get_routing_resources() -> Dict[str, float]:
"""Currently we only use default parameter settings. """Currently we only use default parameter settings.
In the future, for specific project, the resources may need to be tuned. In the future, for specific project, the resources may need to be tuned.
Returns: Returns:
Routing resources. Routing resources.
""" """
...@@ -703,7 +686,6 @@ def save_placement_with_info(plc: plc_client.PlacementCost, ...@@ -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} Routes used by macros, hor : {hor_macro_alloc:.3f} ver : {ver_macro_alloc:.3f}
Smoothing factor : {smooth} Smoothing factor : {smooth}
Use incremental cost : {incr_cost} Use incremental cost : {incr_cost}
To view this file (most options are default): To view this file (most options are default):
viewer_binary\ viewer_binary\
--netlist_file {src_filename}\ --netlist_file {src_filename}\
...@@ -907,12 +889,10 @@ def grid_locations_near(plc: plc_client.PlacementCost, ...@@ -907,12 +889,10 @@ def grid_locations_near(plc: plc_client.PlacementCost,
def place_near(plc: plc_client.PlacementCost, node_index: int, def place_near(plc: plc_client.PlacementCost, node_index: int,
location: int) -> bool: location: int) -> bool:
"""Places a node (legally) closest to the given location. """Places a node (legally) closest to the given location.
Args: Args:
plc: placement_cost object. plc: placement_cost object.
node_index: index of a node. node_index: index of a node.
location: target grid cell location. (row * num_cols + num_cols) location: target grid cell location. (row * num_cols + num_cols)
Returns: Returns:
True on success, False if this node was not placed on any grid legally. True on success, False if this node was not placed on any grid legally.
""" """
......
...@@ -74,15 +74,18 @@ class PlacementCost(object): ...@@ -74,15 +74,18 @@ class PlacementCost(object):
# All modules look-up table # All modules look-up table
self.modules = [] self.modules = []
self.modules_w_pins = [] self.modules_w_pins = []
# modules to index look-up table # modules to index look-up table
self.indices_to_mod_name = {} self.indices_to_mod_name = {}
self.mod_name_to_indices = {} self.mod_name_to_indices = {}
# indices storage # indices storage
self.port_indices = [] self.port_indices = []
self.hard_macro_indices = [] self.hard_macro_indices = []
self.hard_macro_pin_indices = [] self.hard_macro_pin_indices = []
self.soft_macro_indices = [] self.soft_macro_indices = []
self.soft_macro_pin_indices = [] self.soft_macro_pin_indices = []
# macro to pins look-up table: [MACRO_NAME] => [PIN_NAME] # macro to pins look-up table: [MACRO_NAME] => [PIN_NAME]
self.hard_macros_to_inpins = {} self.hard_macros_to_inpins = {}
self.soft_macros_to_inpins = {} self.soft_macros_to_inpins = {}
...@@ -90,7 +93,7 @@ class PlacementCost(object): ...@@ -90,7 +93,7 @@ class PlacementCost(object):
# Placed macro # Placed macro
self.placed_macro = [] self.placed_macro = []
# unknown # not used
self.use_incremental_cost = False self.use_incremental_cost = False
# blockage # blockage
self.blockages = [] self.blockages = []
...@@ -100,11 +103,12 @@ class PlacementCost(object): ...@@ -100,11 +103,12 @@ class PlacementCost(object):
# default canvas width/height based on cell area # default canvas width/height based on cell area
self.width = math.sqrt(self.get_area()/0.6) self.width = math.sqrt(self.get_area()/0.6)
self.height = math.sqrt(self.get_area()/0.6) self.height = math.sqrt(self.get_area()/0.6)
# default gridding # default gridding
self.grid_col = 10 self.grid_col = 10
self.grid_row = 10 self.grid_row = 10
# initialize congestion map # initialize congestion map
# TODO recompute after new gridding
self.V_routing_cong = [0] * (self.grid_col * self.grid_row) self.V_routing_cong = [0] * (self.grid_col * self.grid_row)
self.H_routing_cong = [0] * (self.grid_col * self.grid_row) self.H_routing_cong = [0] * (self.grid_col * self.grid_row)
self.V_macro_routing_cong = [0] * (self.grid_col * self.grid_row) self.V_macro_routing_cong = [0] * (self.grid_col * self.grid_row)
...@@ -112,6 +116,7 @@ class PlacementCost(object): ...@@ -112,6 +116,7 @@ class PlacementCost(object):
# initial grid mask, flatten before output # initial grid mask, flatten before output
self.node_mask = np.array([1] * (self.grid_col * self.grid_row))\ self.node_mask = np.array([1] * (self.grid_col * self.grid_row))\
.reshape(self.grid_row, self.grid_col) .reshape(self.grid_row, self.grid_col)
# store module/component count # store module/component count
self.ports_cnt = len(self.port_indices) self.ports_cnt = len(self.port_indices)
self.hard_macro_cnt = len(self.hard_macro_indices) self.hard_macro_cnt = len(self.hard_macro_indices)
...@@ -119,6 +124,7 @@ class PlacementCost(object): ...@@ -119,6 +124,7 @@ class PlacementCost(object):
self.soft_macros_cnt = len(self.soft_macro_indices) self.soft_macros_cnt = len(self.soft_macro_indices)
self.soft_macro_pins_cnt = len(self.soft_macro_pin_indices) self.soft_macro_pins_cnt = len(self.soft_macro_pin_indices)
self.module_cnt = self.hard_macro_cnt + self.soft_macros_cnt + self.ports_cnt self.module_cnt = self.hard_macro_cnt + self.soft_macros_cnt + self.ports_cnt
# assert module and pin count are correct # assert module and pin count are correct
assert (len(self.modules)) == self.module_cnt assert (len(self.modules)) == self.module_cnt
assert (len(self.modules_w_pins) - \ assert (len(self.modules_w_pins) - \
...@@ -136,7 +142,7 @@ class PlacementCost(object): ...@@ -136,7 +142,7 @@ class PlacementCost(object):
def __read_protobuf(self): def __read_protobuf(self):
""" """
Protobuf Netlist Parser private function: Protobuf Netlist Parser
""" """
with open(self.netlist_file) as fp: with open(self.netlist_file) as fp:
line = fp.readline() line = fp.readline()
...@@ -159,7 +165,6 @@ class PlacementCost(object): ...@@ -159,7 +165,6 @@ class PlacementCost(object):
# node found # node found
if line_item[0] == 'node': if line_item[0] == 'node':
node_cnt += 1
node_name = '' node_name = ''
input_list = [] input_list = []
...@@ -169,9 +174,15 @@ class PlacementCost(object): ...@@ -169,9 +174,15 @@ class PlacementCost(object):
# retrieve node name # retrieve node name
if line_item[0] == 'name': if line_item[0] == 'name':
node_name = line_item[1] node_name = line_item[1]
# skip metadata header
if node_name == "__metadata__":
pass
else:
node_cnt += 1
else: else:
node_name = 'N/A name' node_name = 'N/A name'
# advance ptr # advance ptr
line = fp.readline() line = fp.readline()
line_item = re.findall(r'\w+[^\:\n\\{\}\s"]*', line) line_item = re.findall(r'\w+[^\:\n\\{\}\s"]*', line)
...@@ -198,6 +209,23 @@ class PlacementCost(object): ...@@ -198,6 +209,23 @@ class PlacementCost(object):
line_item = re.findall(r'\w+', line) line_item = re.findall(r'\w+', line)
key = line_item[1] key = line_item[1]
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+[^\:\n\\{\}\s"]*', line)
attr_dict[key] = line_item
line = fp.readline()
line = fp.readline()
line = fp.readline()
line_item = re.findall(r'\w+', line)
else:
# advance, expect value # advance, expect value
line = fp.readline() line = fp.readline()
line_item = re.findall(r'\w+', line) line_item = re.findall(r'\w+', line)
...@@ -214,18 +242,21 @@ class PlacementCost(object): ...@@ -214,18 +242,21 @@ class PlacementCost(object):
line_item = re.findall(r'\w+', line) line_item = re.findall(r'\w+', line)
if attr_dict['type'][1] == 'macro': if node_name == "__metadata__":
# skipping metadata header
logging.info('[INFO NETLIST PARSER] skipping invalid net input')
elif attr_dict['type'][1] == 'macro':
# soft macro # soft macro
# check if all required information is obtained # check if all required information is obtained
try: try:
assert 'x' in attr_dict.keys() assert 'x' in attr_dict.keys()
except AssertionError: except AssertionError:
logging.warning('[NETLIST PARSER ERROR] x is not defined') logging.warning('[ERROR NETLIST PARSER] x is not defined')
try: try:
assert 'y' in attr_dict.keys() assert 'y' in attr_dict.keys()
except AssertionError: 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], soft_macro = self.SoftMacro(name=node_name, width=attr_dict['width'][1],
height = attr_dict['height'][1], height = attr_dict['height'][1],
...@@ -248,16 +279,18 @@ class PlacementCost(object): ...@@ -248,16 +279,18 @@ class PlacementCost(object):
y = attr_dict['y'][1], y = attr_dict['y'][1],
macro_name = attr_dict['macro_name'][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: if input_list:
# net count should be factored by net weight
if 'weight' in attr_dict.keys(): if 'weight' in attr_dict.keys():
self.net_cnt += 1 * float(attr_dict['weight'][1]) self.net_cnt += 1 * float(attr_dict['weight'][1])
else: else:
self.net_cnt += 1 self.net_cnt += 1
soft_macro_pin.add_sinks(input_list) 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) self.modules_w_pins.append(soft_macro_pin)
# mapping node_name ==> node idx # mapping node_name ==> node idx
self.mod_name_to_indices[node_name] = node_cnt-1 self.mod_name_to_indices[node_name] = node_cnt-1
...@@ -301,10 +334,14 @@ class PlacementCost(object): ...@@ -301,10 +334,14 @@ class PlacementCost(object):
x_offset = attr_dict['x_offset'][1], x_offset = attr_dict['x_offset'][1],
y_offset = attr_dict['y_offset'][1], y_offset = attr_dict['y_offset'][1],
macro_name = attr_dict['macro_name'][1]) macro_name = attr_dict['macro_name'][1])
# if net weight is defined, set weight
if 'weight' in attr_dict.keys(): if 'weight' in attr_dict.keys():
hard_macro_pin.set_weight(float(attr_dict['weight'][1])) hard_macro_pin.set_weight(float(attr_dict['weight'][1]))
# if pin has net info
if input_list: if input_list:
# net count should be factored by net weight
if 'weight' in attr_dict.keys(): if 'weight' in attr_dict.keys():
self.net_cnt += 1 * float(attr_dict['weight'][1]) self.net_cnt += 1 * float(attr_dict['weight'][1])
else: else:
...@@ -334,6 +371,7 @@ class PlacementCost(object): ...@@ -334,6 +371,7 @@ class PlacementCost(object):
y = attr_dict['y'][1], y = attr_dict['y'][1],
side = attr_dict['side'][1]) side = attr_dict['side'][1])
# if pin has net info
if input_list: if input_list:
self.net_cnt += 1 self.net_cnt += 1
port.add_sinks(input_list) port.add_sinks(input_list)
...@@ -512,7 +550,7 @@ class PlacementCost(object): ...@@ -512,7 +550,7 @@ class PlacementCost(object):
traceback.print_tb(tb) traceback.print_tb(tb)
tb_info = traceback.extract_tb(tb) tb_info = traceback.extract_tb(tb)
_, line, _, text = tb_info[-1] _, 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)) .format(line, text))
exit(1) exit(1)
...@@ -522,7 +560,7 @@ class PlacementCost(object): ...@@ -522,7 +560,7 @@ class PlacementCost(object):
self.hard_macro_indices +\ self.hard_macro_indices +\
self.soft_macro_indices) == list(info_dict['node_plc'].keys()) self.soft_macro_indices) == list(info_dict['node_plc'].keys())
except AssertionError: 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.hard_macro_indices +\
self.soft_macro_indices)), len(list(info_dict['node_plc'].keys()))) self.soft_macro_indices)), len(list(info_dict['node_plc'].keys())))
exit(1) exit(1)
...@@ -535,7 +573,7 @@ class PlacementCost(object): ...@@ -535,7 +573,7 @@ class PlacementCost(object):
mod_orient = info_dict['node_plc'][mod_idx][2] mod_orient = info_dict['node_plc'][mod_idx][2]
mod_ifFixed = int(info_dict['node_plc'][mod_idx][3]) mod_ifFixed = int(info_dict['node_plc'][mod_idx][3])
except Exception as e: 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.. #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): ...@@ -573,19 +611,21 @@ class PlacementCost(object):
macro = self.modules_w_pins[macro_idx] macro = self.modules_w_pins[macro_idx]
macro_name = macro.get_name() macro_name = macro.get_name()
# Hard macro
if not self.is_node_soft_macro(macro_idx): if not self.is_node_soft_macro(macro_idx):
if macro_name in self.hard_macros_to_inpins.keys(): if macro_name in self.hard_macros_to_inpins.keys():
pin_names = self.hard_macros_to_inpins[macro_name] pin_names = self.hard_macros_to_inpins[macro_name]
else: else:
print("[ERROR UPDATE CONNECTION] MACRO not found") print("[ERROR UPDATE CONNECTION] MACRO pins not found")
exit(1) continue
# use is_node_soft_macro()
# Soft macro
elif self.is_node_soft_macro(macro_idx): elif self.is_node_soft_macro(macro_idx):
if macro_name in self.soft_macros_to_inpins.keys(): if macro_name in self.soft_macros_to_inpins.keys():
pin_names = self.soft_macros_to_inpins[macro_name] pin_names = self.soft_macros_to_inpins[macro_name]
else: else:
print("[ERROR UPDATE CONNECTION] MACRO not found") print("[ERROR UPDATE CONNECTION] macro pins not found")
exit(1) continue
for pin_name in pin_names: for pin_name in pin_names:
pin = self.modules_w_pins[self.mod_name_to_indices[pin_name]] pin = self.modules_w_pins[self.mod_name_to_indices[pin_name]]
...@@ -640,8 +680,10 @@ class PlacementCost(object): ...@@ -640,8 +680,10 @@ class PlacementCost(object):
def __get_pin_position(self, pin_idx): def __get_pin_position(self, pin_idx):
""" """
private function for getting pin location
* PORT = its own position * PORT = its own position
* MACRO PIN = ref position + offset position87654321 * HARD MACRO PIN = ref position + offset position
* SOFT MACRO PIN = ref position
""" """
try: try:
assert (self.modules_w_pins[pin_idx].get_type() == 'MACRO_PIN' or\ assert (self.modules_w_pins[pin_idx].get_type() == 'MACRO_PIN' or\
...@@ -650,19 +692,21 @@ class PlacementCost(object): ...@@ -650,19 +692,21 @@ class PlacementCost(object):
print("[ERROR PIN POSITION] Not a MACRO PIN") print("[ERROR PIN POSITION] Not a MACRO PIN")
exit(1) exit(1)
# Retrieve node that this pin instantiated on
ref_node_idx = self.get_ref_node_id(pin_idx) ref_node_idx = self.get_ref_node_id(pin_idx)
if ref_node_idx == -1: if ref_node_idx == -1:
if self.modules_w_pins[pin_idx].get_type() == 'PORT': if self.modules_w_pins[pin_idx].get_type() == 'PORT':
return self.modules_w_pins[pin_idx].get_pos() return self.modules_w_pins[pin_idx].get_pos()
else: else:
# cannot be 'MACRO' print("[ERROR PIN POSITION] Parent Node Not Found.")
exit(1) exit(1)
# print("ref_node_idx", ref_node_idx) # Parent node
ref_node = self.modules_w_pins[ref_node_idx] ref_node = self.modules_w_pins[ref_node_idx]
ref_node_x, ref_node_y = ref_node.get_pos() 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 = self.modules_w_pins[pin_idx]
pin_node_x_offset, pin_node_y_offset = pin_node.get_offset() pin_node_x_offset, pin_node_y_offset = pin_node.get_offset()
...@@ -685,18 +729,26 @@ class PlacementCost(object): ...@@ -685,18 +729,26 @@ class PlacementCost(object):
# NOTE: connection only defined on PORT, soft/hard macro pins # NOTE: connection only defined on PORT, soft/hard macro pins
if curr_type == "PORT" and mod.get_sink(): if curr_type == "PORT" and mod.get_sink():
# add source position # add source position
x_coord.append(self.__get_pin_position(mod_idx)[0]) x_coord.append(mod.get_pos()[0])
y_coord.append(self.__get_pin_position(mod_idx)[1]) y_coord.append(mod.get_pos()[1])
# get sink
for sink_name in mod.get_sink(): for sink_name in mod.get_sink():
for sink_pin in mod.get_sink()[sink_name]: for sink_pin in mod.get_sink()[sink_name]:
# retrieve indx in modules_w_pins # retrieve indx in modules_w_pins
sink_idx = self.mod_name_to_indices[sink_pin] sink_idx = self.mod_name_to_indices[sink_pin]
# retrieve sink object # retrieve sink object
sink = self.modules_w_pins[sink_idx] 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 # retrieve location
x_coord.append(self.__get_pin_position(sink_idx)[0]) x_coord.append(self.__get_pin_position(sink_idx)[0])
y_coord.append(self.__get_pin_position(sink_idx)[1]) y_coord.append(self.__get_pin_position(sink_idx)[1])
elif curr_type == "MACRO_PIN": 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 # add source position
x_coord.append(self.__get_pin_position(mod_idx)[0]) x_coord.append(self.__get_pin_position(mod_idx)[0])
y_coord.append(self.__get_pin_position(mod_idx)[1]) y_coord.append(self.__get_pin_position(mod_idx)[1])
...@@ -709,7 +761,6 @@ class PlacementCost(object): ...@@ -709,7 +761,6 @@ class PlacementCost(object):
# retrieve indx in modules_w_pins # retrieve indx in modules_w_pins
input_idx = self.mod_name_to_indices[sink_name] input_idx = self.mod_name_to_indices[sink_name]
# retrieve location # retrieve location
# print(self.__get_pin_position(input_idx))
x_coord.append(self.__get_pin_position(input_idx)[0]) x_coord.append(self.__get_pin_position(input_idx)[0])
y_coord.append(self.__get_pin_position(input_idx)[1]) y_coord.append(self.__get_pin_position(input_idx)[1])
...@@ -779,8 +830,9 @@ class PlacementCost(object): ...@@ -779,8 +830,9 @@ class PlacementCost(object):
return float(sum_cong / cong_cnt) return float(sum_cong / cong_cnt)
def get_congestion_cost(self): 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: if self.FLAG_UPDATE_CONGESTION:
self.get_routing() self.get_routing()
...@@ -788,7 +840,7 @@ class PlacementCost(object): ...@@ -788,7 +840,7 @@ class PlacementCost(object):
def __get_grid_cell_location(self, x_pos, y_pos): def __get_grid_cell_location(self, x_pos, y_pos):
""" """
private function for getting grid cell row/col ranging from 0...N private function: for getting grid cell row/col ranging from 0...N
""" """
self.grid_width = float(self.width/self.grid_col) self.grid_width = float(self.width/self.grid_col)
self.grid_height = float(self.height/self.grid_row) self.grid_height = float(self.height/self.grid_row)
...@@ -798,7 +850,7 @@ class PlacementCost(object): ...@@ -798,7 +850,7 @@ class PlacementCost(object):
def __get_grid_location_position(self, col:int, row:int): 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_width = float(self.width/self.grid_col)
self.grid_height = float(self.height/self.grid_row) self.grid_height = float(self.height/self.grid_row)
...@@ -809,7 +861,7 @@ class PlacementCost(object): ...@@ -809,7 +861,7 @@ class PlacementCost(object):
def __get_grid_cell_position(self, grid_cell_idx:int): 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 row = grid_cell_idx // self.grid_col
col = grid_cell_idx % self.grid_col col = grid_cell_idx % self.grid_col
...@@ -823,7 +875,7 @@ class PlacementCost(object): ...@@ -823,7 +875,7 @@ class PlacementCost(object):
mod_height:float 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 row = grid_cell_idx // self.grid_col
col = grid_cell_idx % self.grid_col col = grid_cell_idx % self.grid_col
...@@ -835,19 +887,9 @@ class PlacementCost(object): ...@@ -835,19 +887,9 @@ class PlacementCost(object):
self.node_mask[ row - ver_pad:row + ver_pad + 1, self.node_mask[ row - ver_pad:row + ver_pad + 1,
col - hor_pad:col + hor_pad + 1] = 0 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): 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_min_max = min(block_i.x_max, block_j.x_max)
x_max_min = max(block_i.x_min, block_j.x_min) x_max_min = max(block_i.x_min, block_j.x_min)
...@@ -865,7 +907,7 @@ class PlacementCost(object): ...@@ -865,7 +907,7 @@ class PlacementCost(object):
def __overlap_dist(self, block_i, block_j): 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) 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) 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): ...@@ -875,7 +917,7 @@ class PlacementCost(object):
def __add_module_to_grid_cells(self, mod_x, mod_y, mod_w, mod_h): 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 # Two corners
ur = (mod_x + (mod_w/2), mod_y + (mod_h/2)) ur = (mod_x + (mod_w/2), mod_y + (mod_h/2))
...@@ -927,6 +969,8 @@ class PlacementCost(object): ...@@ -927,6 +969,8 @@ class PlacementCost(object):
self.grid_occupied[self.grid_col * r_i + c_i] += \ self.grid_occupied[self.grid_col * r_i + c_i] += \
self.__overlap_area(grid_cell_block, module_block) self.__overlap_area(grid_cell_block, module_block)
def get_grid_cells_density(self): def get_grid_cells_density(self):
""" """
compute density for all grid cells compute density for all grid cells
...@@ -942,6 +986,11 @@ class PlacementCost(object): ...@@ -942,6 +986,11 @@ class PlacementCost(object):
for module_idx in (self.soft_macro_indices + self.hard_macro_indices): for module_idx in (self.soft_macro_indices + self.hard_macro_indices):
# extract module information # extract module information
module = self.modules_w_pins[module_idx] module = self.modules_w_pins[module_idx]
# skipping unplaced module
if not module.get_placed_flag():
continue
module_h = module.get_height() module_h = module.get_height()
module_w = module.get_width() module_w = module.get_width()
module_x, module_y = module.get_pos() module_x, module_y = module.get_pos()
...@@ -1145,6 +1194,9 @@ class PlacementCost(object): ...@@ -1145,6 +1194,9 @@ class PlacementCost(object):
return self.hrouting_alloc, self.vrouting_alloc return self.hrouting_alloc, self.vrouting_alloc
def __two_pin_net_routing(self, source_gcell, node_gcells, weight): def __two_pin_net_routing(self, source_gcell, node_gcells, weight):
"""
private function: Routing between 2-pin nets
"""
temp_gcell = list(node_gcells) temp_gcell = list(node_gcells)
if temp_gcell[0] == source_gcell: if temp_gcell[0] == source_gcell:
sink_gcell = temp_gcell[1] sink_gcell = temp_gcell[1]
...@@ -1171,7 +1223,10 @@ class PlacementCost(object): ...@@ -1171,7 +1223,10 @@ class PlacementCost(object):
col = sink_gcell[1] col = sink_gcell[1]
self.V_routing_cong[row * self.grid_col + col] += weight 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])) node_gcells.sort(key = lambda x: (x[1], x[0]))
y1, x1 = node_gcells[0] y1, x1 = node_gcells[0]
y2, x2 = node_gcells[1] y2, x2 = node_gcells[1]
...@@ -1195,11 +1250,13 @@ class PlacementCost(object): ...@@ -1195,11 +1250,13 @@ class PlacementCost(object):
for row in range(min(y2, y3), max(y2, y3)): for row in range(min(y2, y3), max(y2, y3)):
col = x3 col = x3
self.V_routing_cong[row * self.grid_col + col] += weight 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() node_gcells.sort()
#print(node_gcells)
y1, x1 = node_gcells[0] y1, x1 = node_gcells[0]
y2, x2 = node_gcells[1] y2, x2 = node_gcells[1]
y3, x3 = node_gcells[2] y3, x3 = node_gcells[2]
...@@ -1220,9 +1277,11 @@ class PlacementCost(object): ...@@ -1220,9 +1277,11 @@ class PlacementCost(object):
for row in range(min(y2, y3), max(y2, y3)): for row in range(min(y2, y3), max(y2, y3)):
col = x3 col = x3
self.V_routing_cong[row * self.grid_col + col] += weight self.V_routing_cong[row * self.grid_col + col] += weight
pass
def __three_pin_net_routing(self, node_gcells, weight): def __three_pin_net_routing(self, node_gcells, weight):
"""
private_function: Routing Scheme for 3-pin nets
"""
temp_gcell = list(node_gcells) temp_gcell = list(node_gcells)
## Sorted based on X ## Sorted based on X
temp_gcell.sort(key = lambda x: (x[1], x[0])) temp_gcell.sort(key = lambda x: (x[1], x[0]))
...@@ -1231,12 +1290,8 @@ class PlacementCost(object): ...@@ -1231,12 +1290,8 @@ class PlacementCost(object):
y3, x3 = temp_gcell[2] y3, x3 = temp_gcell[2]
if x1 < x2 and x2 < x3 and min(y1, y3) < y2 and max(y1, y3) > y2: if x1 < x2 and x2 < x3 and min(y1, y3) < y2 and max(y1, y3) > y2:
# print('sk1') self.__l_routing(temp_gcell, weight)
self.l_routing(temp_gcell, weight) elif x2 == x3 and x1 < x2 and y1 < min(y2, y3):
return
if x2 == x3 and x1 < x2 and y1 < min(y2, y3):
# print('sk2')
for col_idx in range(x1,x2,1): for col_idx in range(x1,x2,1):
row = y1 row = y1
col = col_idx col = col_idx
...@@ -1246,10 +1301,7 @@ class PlacementCost(object): ...@@ -1246,10 +1301,7 @@ class PlacementCost(object):
col = x2 col = x2
row = row_idx row = row_idx
self.V_routing_cong[row * self.grid_col + col] += weight self.V_routing_cong[row * self.grid_col + col] += weight
return elif y2 == y3:
if y2 == y3:
# print('sk3')
for col in range(x1, x2): for col in range(x1, x2):
row = y1 row = y1
self.H_routing_cong[row * self.grid_col + col] += weight self.H_routing_cong[row * self.grid_col + col] += weight
...@@ -1261,12 +1313,8 @@ class PlacementCost(object): ...@@ -1261,12 +1313,8 @@ class PlacementCost(object):
for row in range(min(y2, y1), max(y2, y1)): for row in range(min(y2, y1), max(y2, y1)):
col = x2 col = x2
self.V_routing_cong[row * self.grid_col + col] += weight self.V_routing_cong[row * self.grid_col + col] += weight
return else:
self.__t_routing(temp_gcell, weight)
# print('sk4')
self.t_routing(temp_gcell, weight)
return
def __macro_route_over_grid_cell(self, mod_x, mod_y, mod_w, mod_h): def __macro_route_over_grid_cell(self, mod_x, mod_y, mod_w, mod_h):
""" """
...@@ -1363,6 +1411,9 @@ class PlacementCost(object): ...@@ -1363,6 +1411,9 @@ class PlacementCost(object):
self.H_macro_routing_cong[r_i * self.grid_col + c_i] -= y_dist * self.hrouting_alloc 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): def __split_net(self, source_gcell, node_gcells):
"""
private function: Split >3 pin net into multiple two-pin nets
"""
splitted_netlist = [] splitted_netlist = []
for node_gcell in node_gcells: for node_gcell in node_gcells:
if node_gcell != source_gcell: if node_gcell != source_gcell:
...@@ -1370,14 +1421,18 @@ class PlacementCost(object): ...@@ -1370,14 +1421,18 @@ class PlacementCost(object):
return splitted_netlist return splitted_netlist
def get_vertical_routing_congestion(self): def get_vertical_routing_congestion(self):
# TODO: detect if we need to run """
Return Vertical Routing Congestion
"""
if self.FLAG_UPDATE_CONGESTION: if self.FLAG_UPDATE_CONGESTION:
self.get_routing() self.get_routing()
return self.V_routing_cong return self.V_routing_cong
def get_horizontal_routing_congestion(self): def get_horizontal_routing_congestion(self):
# TODO: detect if we need to run """
Return Horizontal Routing Congestion
"""
if self.FLAG_UPDATE_CONGESTION: if self.FLAG_UPDATE_CONGESTION:
self.get_routing() self.get_routing()
...@@ -1385,7 +1440,7 @@ class PlacementCost(object): ...@@ -1385,7 +1440,7 @@ class PlacementCost(object):
def get_routing(self): def get_routing(self):
""" """
Route between modules H/V Routing Before Computing Routing Congestions
""" """
if self.FLAG_UPDATE_CONGESTION: if self.FLAG_UPDATE_CONGESTION:
self.grid_width = float(self.width/self.grid_col) self.grid_width = float(self.width/self.grid_col)
...@@ -1458,8 +1513,6 @@ class PlacementCost(object): ...@@ -1458,8 +1513,6 @@ class PlacementCost(object):
for curr_net in self.__split_net(source_gcell=source_gcell, node_gcells=node_gcells): 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) 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 # normalize routing congestion
for idx, v_gcell in enumerate(self.V_routing_cong): for idx, v_gcell in enumerate(self.V_routing_cong):
self.V_routing_cong[idx] = float(v_gcell / self.grid_v_routes) self.V_routing_cong[idx] = float(v_gcell / self.grid_v_routes)
...@@ -1480,6 +1533,9 @@ class PlacementCost(object): ...@@ -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)] self.H_routing_cong = [sum(x) for x in zip(self.H_routing_cong, self.H_macro_routing_cong)]
def __smooth_routing_cong(self): def __smooth_routing_cong(self):
"""
Smoothing V/H Routing congestion
"""
temp_V_routing_cong = [0] * self.grid_col * self.grid_row temp_V_routing_cong = [0] * self.grid_col * self.grid_row
temp_H_routing_cong = [0] * self.grid_col * self.grid_row temp_H_routing_cong = [0] * self.grid_col * self.grid_row
...@@ -1525,72 +1581,61 @@ class PlacementCost(object): ...@@ -1525,72 +1581,61 @@ class PlacementCost(object):
def is_node_soft_macro(self, node_idx) -> bool: def is_node_soft_macro(self, node_idx) -> bool:
""" """
Return None or return ref_id Return if node is a soft macro
""" """
try: try:
return node_idx in self.soft_macro_indices return node_idx in self.soft_macro_indices
except IndexError: except IndexError:
print("[ERROR INDEX OUT OF RANGE] Can not process index at {}".format(node_idx)) 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: def is_node_hard_macro(self, node_idx) -> bool:
""" """
Return None or return ref_id Return if node is a hard macro
""" """
try: try:
return node_idx in self.hard_macro_indices return node_idx in self.hard_macro_indices
except IndexError: except IndexError:
print("[ERROR INDEX OUT OF RANGE] Can not process index at {}".format(node_idx)) 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: def get_node_name(self, node_idx: int) -> str:
"""
Return node name based on given node index
"""
try:
return self.indices_to_mod_name[node_idx] return self.indices_to_mod_name[node_idx]
except Exception:
print("[ERROR NODE INDEX] Node not found!")
exit(1)
def _get_node_mask(self, node_idx: int, node_name: str=None) -> list: def get_node_index(self, node_name: str) -> int:
""" """
Return Grid_col x Grid_row: Return node index based on given node name
1 == placable
0 == unplacable
Placement Constraint:
- center @ grid cell
- no overlapping other macro
- no OOB
""" """
if self.FLAG_UPDATE_NODE_MASK: try:
self.__update_node_mask() return self.mod_name_to_indices[node_name]
except Exception:
module = self.modules_w_pins[node_idx] print("[ERROR NODE NAME] Node not found!")
exit(1)
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()
def get_node_mask(self, node_idx: int, node_name: str=None) -> list: 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] mod = self.modules_w_pins[node_idx]
canvas_block = Block(x_max=self.width, canvas_block = Block(x_max=self.width,
y_max=self.height, y_max=self.height,
x_min=0, x_min=0,
y_min=0) y_min=0)
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_w = mod.get_width()
mod_h = mod.get_height() mod_h = mod.get_height()
...@@ -1600,15 +1645,10 @@ class PlacementCost(object): ...@@ -1600,15 +1645,10 @@ class PlacementCost(object):
self.grid_width = float(self.width/self.grid_col) self.grid_width = float(self.width/self.grid_col)
self.grid_height = float(self.height/self.grid_row) self.grid_height = float(self.height/self.grid_row)
# print(self.grid_col, self.grid_row)
# print(mod_w*mod_h)
for i in range(self.grid_row): for i in range(self.grid_row):
for j in range(self.grid_col): for j in range(self.grid_col):
# try every location # 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_x = j * self.grid_width + (self.grid_width/2)
temp_y = i * self.grid_height + (self.grid_height/2) temp_y = i * self.grid_height + (self.grid_height/2)
...@@ -1621,10 +1661,9 @@ class PlacementCost(object): ...@@ -1621,10 +1661,9 @@ class PlacementCost(object):
# check OOB # check OOB
if abs(self.__overlap_area( if abs(self.__overlap_area(
block_i=canvas_block, block_j=mod_block) - (mod_w*mod_h)) > 1e-8: 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 temp_node_mask[i][j] = 0
else: else:
# check overlapping
for pmod_idx in self.placed_macro: for pmod_idx in self.placed_macro:
pmod = self.modules_w_pins[pmod_idx] pmod = self.modules_w_pins[pmod_idx]
if not pmod.get_placed_flag(): if not pmod.get_placed_flag():
...@@ -1634,12 +1673,12 @@ class PlacementCost(object): ...@@ -1634,12 +1673,12 @@ class PlacementCost(object):
p_w = pmod.get_width() p_w = pmod.get_width()
p_h = pmod.get_height() p_h = pmod.get_height()
pmod_block = Block( pmod_block = Block(
x_max=p_x + (p_w/2) + 1, x_max=p_x + (p_w/2),
y_max=p_y + (p_h/2) + 1, y_max=p_y + (p_h/2),
x_min=p_x - (p_w/2) - 1, x_min=p_x - (p_w/2),
y_min=p_y - (p_h/2) - 1 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: if self.__overlap_area(block_i=pmod_block, block_j=mod_block) > 0:
temp_node_mask[i][j] = 0 temp_node_mask[i][j] = 0
...@@ -1654,7 +1693,7 @@ class PlacementCost(object): ...@@ -1654,7 +1693,7 @@ class PlacementCost(object):
return self.modules_w_pins[node_idx].get_type() return self.modules_w_pins[node_idx].get_type()
except IndexError: except IndexError:
# NOTE: Google's API return NONE if out of range # 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 return None
...@@ -1665,7 +1704,7 @@ class PlacementCost(object): ...@@ -1665,7 +1704,7 @@ class PlacementCost(object):
mod = None mod = None
try: try:
mod = self.modules_w_pins[node_idx] 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: except AssertionError:
print("[ERROR NODE FIXED] Found {}. Only 'MACRO', 'macro', 'STDCELL'".format(mod.get_type()) print("[ERROR NODE FIXED] Found {}. Only 'MACRO', 'macro', 'STDCELL'".format(mod.get_type())
+"'PORT' are considered to be fixable nodes") +"'PORT' are considered to be fixable nodes")
...@@ -1677,12 +1716,40 @@ class PlacementCost(object): ...@@ -1677,12 +1716,40 @@ class PlacementCost(object):
return mod.get_width(), mod.get_height() return mod.get_width(), mod.get_height()
def make_soft_macros_square(self): 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): def set_use_incremental_cost(self, use_incremental_cost):
"""
NOT IMPLEMENTED
"""
self.use_incremental_cost = use_incremental_cost self.use_incremental_cost = use_incremental_cost
def get_use_incremental_cost(self): def get_use_incremental_cost(self):
"""
NOT IMPLEMENTED
"""
return self.use_incremental_cost return self.use_incremental_cost
def get_macro_adjacency(self) -> list: def get_macro_adjacency(self) -> list:
...@@ -1791,8 +1858,6 @@ class PlacementCost(object): ...@@ -1791,8 +1858,6 @@ class PlacementCost(object):
# instantiate clustered ports # instantiate clustered ports
for row_idx, cluster_cell in enumerate(sorted(clustered_ports, key=lambda tup: tup[1])): 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 # add cell location
cell_location[row_idx] = cluster_cell[0] * self.grid_col + cluster_cell[1] cell_location[row_idx] = cluster_cell[0] * self.grid_col + cluster_cell[1]
...@@ -1803,7 +1868,6 @@ class PlacementCost(object): ...@@ -1803,7 +1868,6 @@ class PlacementCost(object):
for curr_port in clustered_ports[cluster_cell]: for curr_port in clustered_ports[cluster_cell]:
# get module name # get module name
curr_port_name = curr_port.get_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 # assuming ports only connects to macros
for col_idx, h_module_idx in enumerate(module_indices): for col_idx, h_module_idx in enumerate(module_indices):
# col index # col index
...@@ -1813,8 +1877,6 @@ class PlacementCost(object): ...@@ -1813,8 +1877,6 @@ class PlacementCost(object):
# get connected module name # get connected module name
h_module_name = h_module.get_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(): if curr_port_name in h_module.get_connection():
entry += h_module.get_connection()[curr_port_name] entry += h_module.get_connection()[curr_port_name]
...@@ -2084,13 +2146,21 @@ class PlacementCost(object): ...@@ -2084,13 +2146,21 @@ class PlacementCost(object):
exit(1) exit(1)
if not mod.get_fix_flag(): if not mod.get_fix_flag():
mod.set_placed_flag(True) if node_idx in self.hard_macro_indices:
mod.set_placed_flag(False)
self.placed_macro.remove(node_idx) self.placed_macro.remove(node_idx)
# update flag # update flag
self.FLAG_UPDATE_CONGESTION = True self.FLAG_UPDATE_CONGESTION = True
self.FLAG_UPDATE_DENSITY = True self.FLAG_UPDATE_DENSITY = True
# self.FLAG_UPDATE_NODE_MASK = True # placeholder # self.FLAG_UPDATE_NODE_MASK = True # placeholder
self.FLAG_UPDATE_WIRELENGTH = True 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: else:
print("[WARNING UNPLACE NODE] Trying to unplace a fixed node") print("[WARNING UNPLACE NODE] Trying to unplace a fixed node")
...@@ -2135,7 +2205,8 @@ class PlacementCost(object): ...@@ -2135,7 +2205,8 @@ class PlacementCost(object):
pass pass
def get_source_filename(self): def get_source_filename(self):
"""return netlist path """
return netlist path
""" """
return self.netlist_file return self.netlist_file
...@@ -2146,18 +2217,16 @@ class PlacementCost(object): ...@@ -2146,18 +2217,16 @@ class PlacementCost(object):
self.blockages.append([minx, miny, maxx, maxy, blockage_rate]) self.blockages.append([minx, miny, maxx, maxy, blockage_rate])
def get_ref_node_id(self, node_idx=-1): def get_ref_node_id(self, node_idx=-1):
"""ref_node_id is used for macro_pins. Refers to the macro it belongs to. """
ref_node_id is used for macro_pins. Refers to the macro it belongs to.
""" """
if node_idx != -1: if node_idx != -1:
if node_idx in self.soft_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()]
elif node_idx in self.hard_macro_pin_indices:
pin = self.modules_w_pins[node_idx] pin = self.modules_w_pins[node_idx]
return self.mod_name_to_indices[pin.get_macro_name()] return self.mod_name_to_indices[pin.get_macro_name()]
return -1 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
""" """
...@@ -2187,6 +2256,9 @@ class PlacementCost(object): ...@@ -2187,6 +2256,9 @@ class PlacementCost(object):
def display_canvas( self, def display_canvas( self,
annotate=True, annotate=True,
amplify=False): amplify=False):
"""
Non-google function, For quick canvas view
"""
#define Matplotlib figure and axis #define Matplotlib figure and axis
fig, ax = plt.subplots(figsize=(8,8), dpi=50) fig, ax = plt.subplots(figsize=(8,8), dpi=50)
...@@ -2407,6 +2479,12 @@ class PlacementCost(object): ...@@ -2407,6 +2479,12 @@ class PlacementCost(object):
def get_width(self): def get_width(self):
return self.width 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): def set_location(self, grid_cell_idx):
self.location = grid_cell_idx self.location = grid_cell_idx
...@@ -2671,7 +2749,7 @@ class PlacementCost(object): ...@@ -2671,7 +2749,7 @@ class PlacementCost(object):
def main(): def main():
test_netlist_dir = './Plc_client/test/'+\ test_netlist_dir = './Plc_client/test/'+\
'ariane133' 'ariane_68_1.3'
netlist_file = os.path.join(test_netlist_dir, netlist_file = os.path.join(test_netlist_dir,
'netlist.pb.txt') 'netlist.pb.txt')
plc = PlacementCost(netlist_file) plc = PlacementCost(netlist_file)
......
from ast import Assert
import numpy as np import numpy as np
import pandas as pd import pandas as pd
import sys import sys
...@@ -44,20 +45,19 @@ Example: ...@@ -44,20 +45,19 @@ Example:
--marv 51.790\ --marv 51.790\
--smooth 2 --smooth 2
$ python3 -m Plc_client.plc_client_os_test --netlist ./Plc_client/test/ariane133/netlist.pb.txt\ $ python3 -m Plc_client.plc_client_os_test --netlist ./Plc_client/test/ariane_68_1.3/netlist.pb.txt\
--plc ./Plc_client/test/ariane133/initial.plc\ --plc ./Plc_client/test/ariane_68_1.3/legalized.plc\
--width 1599\ --width 1347.100\
--height 1600.06\ --height 1346.800\
--col 24\ --col 23\
--row 21\ --row 28\
--rpmh 10\ --rpmh 11.285\
--rpmv 10\ --rpmv 12.605\
--marh 5\ --marh 7.143\
--marv 5\ --marv 8.339\
--smooth 2 --smooth 2
$ python3 -m Plc_client.plc_client_os_test --netlist ./Plc_client/test/0P2M0m/netlist.pb.txt\ $ 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\ --width 500\
--height 500\ --height 500\
--col 5\ --col 5\
...@@ -138,7 +138,7 @@ class PlacementCostTest(): ...@@ -138,7 +138,7 @@ class PlacementCostTest():
self.plc_os.set_placement_grid(self.GRID_COL, self.GRID_ROW) self.plc_os.set_placement_grid(self.GRID_COL, self.GRID_ROW)
if self.PLC_PATH: 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.set_canvas_boundary_check(False)
self.plc_os.restore_placement(self.PLC_PATH, self.plc_os.restore_placement(self.PLC_PATH,
ifInital=True, ifInital=True,
...@@ -147,7 +147,7 @@ class PlacementCostTest(): ...@@ -147,7 +147,7 @@ class PlacementCostTest():
self.plc.set_canvas_boundary_check(False) self.plc.set_canvas_boundary_check(False)
self.plc.restore_placement(self.PLC_PATH) self.plc.restore_placement(self.PLC_PATH)
else: else:
print("[PLC FILE MISSING] Using only netlist info") print("#[PLC FILE MISSING] Using only netlist info")
try: try:
assert int(self.plc_os.get_area()) == int(self.plc.get_area()) assert int(self.plc_os.get_area()) == int(self.plc.get_area())
...@@ -223,7 +223,7 @@ class PlacementCostTest(): ...@@ -223,7 +223,7 @@ class PlacementCostTest():
self.plc_os.set_placement_grid(self.GRID_COL, self.GRID_ROW) self.plc_os.set_placement_grid(self.GRID_COL, self.GRID_ROW)
if self.PLC_PATH: 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.set_canvas_boundary_check(False)
self.plc_os.restore_placement(self.PLC_PATH, self.plc_os.restore_placement(self.PLC_PATH,
ifInital=ifInital, ifInital=ifInital,
...@@ -246,7 +246,7 @@ class PlacementCostTest(): ...@@ -246,7 +246,7 @@ class PlacementCostTest():
print("overlap_threshold default", self.plc.get_overlap_threshold()) print("overlap_threshold default", self.plc.get_overlap_threshold())
if self.PLC_PATH: 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.set_canvas_boundary_check(False)
self.plc_os.restore_placement(self.PLC_PATH, self.plc_os.restore_placement(self.PLC_PATH,
ifInital=True, ifInital=True,
...@@ -255,7 +255,7 @@ class PlacementCostTest(): ...@@ -255,7 +255,7 @@ class PlacementCostTest():
self.plc.set_canvas_boundary_check(False) self.plc.set_canvas_boundary_check(False)
self.plc.restore_placement(self.PLC_PATH) self.plc.restore_placement(self.PLC_PATH)
else: 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.set_routes_per_micron(self.RPMH, self.RPMV)
self.plc_os.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(): ...@@ -271,7 +271,11 @@ class PlacementCostTest():
self.plc_os.set_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT) self.plc_os.set_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT)
self.plc_os.set_placement_grid(self.GRID_COL, self.GRID_ROW) self.plc_os.set_placement_grid(self.GRID_COL, self.GRID_ROW)
# 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: if False:
self.plc.create_blockage(0.0, 100.0, 300.0, 300.0, 1.0) self.plc.create_blockage(0.0, 100.0, 300.0, 300.0, 1.0)
self.plc.create_blockage(300, 0, 500, 200, 1) self.plc.create_blockage(300, 0, 500, 200, 1)
...@@ -280,16 +284,19 @@ class PlacementCostTest(): ...@@ -280,16 +284,19 @@ class PlacementCostTest():
print(self.plc.set_use_incremental_cost(True)) print(self.plc.set_use_incremental_cost(True))
print(self.plc_os.get_soft_macros_count()) print(self.plc_os.get_soft_macros_count())
# self.plc_os.display_canvas(annotate=False)
# HPWL # HPWL
try: try:
assert int(self.plc_os.get_wirelength()) == int( assert int(self.plc_os.get_wirelength()) == int(self.plc.get_wirelength())
self.plc.get_wirelength()) assert abs(self.plc.get_cost() - self.plc_os.get_cost()) <= 1e-2
assert abs(self.plc.get_cost() - self.plc_os.get_cost()) <= 1e-3 print("#[INFO WIRELENGTH] Matched Wirelength cost -- GL {}, OS {}".format(
print("#[INFO WIRELENGTH] Matched irelength cost -- GL {}, OS {}".format(
str(self.plc.get_cost()), self.plc_os.get_cost())) str(self.plc.get_cost()), self.plc_os.get_cost()))
except Exception as e: except Exception as e:
print("[ERROR WIRELENGTH] Discrepancies found when computing wirelength -- GL {}, OS {}".format( print("[ERROR WIRELENGTH] Discrepancies found when computing wirelength -- GL {}, OS {}".format(
str(self.plc.get_cost()), self.plc_os.get_cost())) 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) exit(1)
# Density # Density
...@@ -310,7 +317,7 @@ class PlacementCostTest(): ...@@ -310,7 +317,7 @@ class PlacementCostTest():
# NOTE: [IGNORE] grid-wise congestion not tested because # NOTE: [IGNORE] grid-wise congestion not tested because
# miscellaneous implementation differences. # miscellaneous implementation differences.
assert abs(self.plc.get_congestion_cost() - 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( print("#[INFO CONGESTION] Matched congestion cost -- GL {}, OS {}".format(
str(self.plc.get_congestion_cost()), self.plc_os.get_congestion_cost())) str(self.plc.get_congestion_cost()), self.plc_os.get_congestion_cost()))
except Exception as e: except Exception as e:
...@@ -324,6 +331,7 @@ class PlacementCostTest(): ...@@ -324,6 +331,7 @@ class PlacementCostTest():
print(" +++++++++++++++++++++++++++++") print(" +++++++++++++++++++++++++++++")
def test_miscellaneous(self): def test_miscellaneous(self):
print("****************** miscellaneous ******************")
# Google's Binary Executable # Google's Binary Executable
self.plc = plc_client.PlacementCost(self.NETLIST_PATH) self.plc = plc_client.PlacementCost(self.NETLIST_PATH)
self.plc_os = plc_client_os.PlacementCost(netlist_file=self.NETLIST_PATH, self.plc_os = plc_client_os.PlacementCost(netlist_file=self.NETLIST_PATH,
...@@ -342,11 +350,10 @@ class PlacementCostTest(): ...@@ -342,11 +350,10 @@ class PlacementCostTest():
self.plc_os.set_placement_grid(self.GRID_COL, self.GRID_ROW) self.plc_os.set_placement_grid(self.GRID_COL, self.GRID_ROW)
self.plc_os.display_canvas() 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_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(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_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT)
# self.plc.set_placement_grid(self.GRID_COL, self.GRID_ROW) # 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_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT)
...@@ -366,6 +373,127 @@ class PlacementCostTest(): ...@@ -366,6 +373,127 @@ class PlacementCostTest():
# print("can_place_node", self.plc.can_place_node(0, 1)) # print("can_place_node", self.plc.can_place_node(0, 1))
print("***************************************************") 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): def test_proxy_congestion(self):
# Google's API # Google's API
self.plc = plc_client.PlacementCost(self.NETLIST_PATH) self.plc = plc_client.PlacementCost(self.NETLIST_PATH)
...@@ -387,7 +515,7 @@ class PlacementCostTest(): ...@@ -387,7 +515,7 @@ class PlacementCostTest():
self.plc_os.set_placement_grid(self.GRID_COL, self.GRID_ROW) self.plc_os.set_placement_grid(self.GRID_COL, self.GRID_ROW)
if self.PLC_PATH: 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.set_canvas_boundary_check(False)
self.plc_os.restore_placement(self.PLC_PATH, self.plc_os.restore_placement(self.PLC_PATH,
ifInital=True, ifInital=True,
...@@ -396,7 +524,7 @@ class PlacementCostTest(): ...@@ -396,7 +524,7 @@ class PlacementCostTest():
self.plc.set_canvas_boundary_check(False) self.plc.set_canvas_boundary_check(False)
self.plc.restore_placement(self.PLC_PATH) self.plc.restore_placement(self.PLC_PATH)
else: 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_gl_h = np.array(self.plc.get_horizontal_routing_congestion())
temp_os_h = np.array(self.plc_os.get_horizontal_routing_congestion()) temp_os_h = np.array(self.plc_os.get_horizontal_routing_congestion())
...@@ -461,7 +589,7 @@ class PlacementCostTest(): ...@@ -461,7 +589,7 @@ class PlacementCostTest():
CELL_IDX = 0 CELL_IDX = 0
r = (CELL_IDX // 35) r = (CELL_IDX // 35)
c = int(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_h_rt[CELL_IDX], temp_os_h_rt[CELL_IDX])
print(temp_gl_v_rt[CELL_IDX], temp_os_v_rt[CELL_IDX]) print(temp_gl_v_rt[CELL_IDX], temp_os_v_rt[CELL_IDX])
...@@ -475,6 +603,7 @@ class PlacementCostTest(): ...@@ -475,6 +603,7 @@ class PlacementCostTest():
assert self.PLC_PATH assert self.PLC_PATH
except AssertionError: except AssertionError:
print("[ERROR PLACEMENT UTIL TEST] Facilitate required .plc file") print("[ERROR PLACEMENT UTIL TEST] Facilitate required .plc file")
exit(1)
self.plc_util = placement_util.create_placement_cost( self.plc_util = placement_util.create_placement_cost(
plc_client=plc_client, plc_client=plc_client,
...@@ -537,6 +666,7 @@ class PlacementCostTest(): ...@@ -537,6 +666,7 @@ class PlacementCostTest():
except AssertionError: except AssertionError:
print("[ERROR PLACEMENT UTIL] Saved PLC Discrepency found at line {}".format( print("[ERROR PLACEMENT UTIL] Saved PLC Discrepency found at line {}".format(
str(idx))) str(idx)))
exit(1)
# if keep plc file for detailed comparison # if keep plc file for detailed comparison
if not keep_save_file: if not keep_save_file:
...@@ -570,6 +700,7 @@ class PlacementCostTest(): ...@@ -570,6 +700,7 @@ class PlacementCostTest():
assert self.PLC_PATH assert self.PLC_PATH
except AssertionError: except AssertionError:
print("[ERROR OBSERVATION EXTRACTOR TEST] Facilitate required .plc file") print("[ERROR OBSERVATION EXTRACTOR TEST] Facilitate required .plc file")
exit(1)
# Using the default edge/node # Using the default edge/node
self._observation_config = observation_config.ObservationConfig( self._observation_config = observation_config.ObservationConfig(
...@@ -581,27 +712,23 @@ class PlacementCostTest(): ...@@ -581,27 +712,23 @@ class PlacementCostTest():
init_placement=self.PLC_PATH init_placement=self.PLC_PATH
) )
self.plc_util.unplace_all_nodes()
self.plc_util_os = placement_util.create_placement_cost( self.plc_util_os = placement_util.create_placement_cost(
plc_client=plc_client_os, plc_client=plc_client_os,
netlist_file=self.NETLIST_PATH, netlist_file=self.NETLIST_PATH,
init_placement=self.PLC_PATH init_placement=self.PLC_PATH
) )
self.plc_util_os.unplace_all_nodes()
if self.PLC_PATH: 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_util_os.set_canvas_boundary_check(False)
self.plc_os.restore_placement(self.PLC_PATH, self.plc_util_os.restore_placement(self.PLC_PATH,
ifInital=True, ifInital=True,
ifValidate=True, ifValidate=True,
ifReadComment=False) ifReadComment=False)
self.plc.set_canvas_boundary_check(False) self.plc_util.set_canvas_boundary_check(False)
self.plc.restore_placement(self.PLC_PATH) self.plc_util.restore_placement(self.PLC_PATH)
else: else:
print("[PLC FILE MISSING] Using only netlist info") print("#[PLC FILE MISSING] Using only netlist info")
self.extractor = observation_extractor.ObservationExtractor( self.extractor = observation_extractor.ObservationExtractor(
plc=self.plc_util, observation_config=self._observation_config plc=self.plc_util, observation_config=self._observation_config
...@@ -610,13 +737,27 @@ class PlacementCostTest(): ...@@ -610,13 +737,27 @@ class PlacementCostTest():
self.extractor_os = observation_extractor.ObservationExtractor( self.extractor_os = observation_extractor.ObservationExtractor(
plc=self.plc_util_os, observation_config=self._observation_config 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 features that are invariant across training steps
static_feature_gl = self.extractor._extract_static_features() static_feature_gl = self.extractor._extract_static_features()
static_feature_os = self.extractor_os._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): for feature_gl, feature_os in zip(static_feature_gl, static_feature_os):
try:
assert (static_feature_gl[feature_gl] == assert (static_feature_gl[feature_gl] ==
static_feature_os[feature_os]).all() 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(" ++++++++++++++++++++++++++++++++++++++++")
print(" +++ TEST OBSERVATION EXTRACTOR: PASS +++") print(" +++ TEST OBSERVATION EXTRACTOR: PASS +++")
...@@ -650,22 +791,36 @@ class PlacementCostTest(): ...@@ -650,22 +791,36 @@ class PlacementCostTest():
self.plc_util_os.set_canvas_size(self.CANVAS_WIDTH, self.CANVAS_HEIGHT) 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) 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( ordered_node_gl = placement_util.get_ordered_node_indices(
mode='descending_size_macro_first', plc=self.plc_util) mode='descending_size_macro_first', plc=self.plc_util)
ordered_node_os = placement_util.get_ordered_node_indices( ordered_node_os = placement_util.get_ordered_node_indices(
mode='descending_size_macro_first', plc=self.plc_util_os) mode='descending_size_macro_first', plc=self.plc_util_os)
try:
assert (np.array(ordered_node_gl) == np.array(ordered_node_os)).all() assert (np.array(ordered_node_gl) == np.array(ordered_node_os)).all()
except:
print("[ERROR PLACE NODE] Node Ordering not matching!")
# Initialize Placement # Initialize Placement
self.plc_util_os.unplace_all_nodes() self.plc_util_os.unplace_all_nodes()
self.plc_util.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 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) self.plc_util_os.display_canvas(annotate=False)
print("OS NODE MASK:")
print(np.flip(np.array(self.plc_util_os.get_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)) 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( 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.GRID_ROW, self.GRID_COL), axis=0))
...@@ -673,11 +828,14 @@ class PlacementCostTest(): ...@@ -673,11 +828,14 @@ class PlacementCostTest():
self.plc_util.place_node(NODE_TO_PLACE_IDX, CELL_TO_PLACE_IDX) self.plc_util.place_node(NODE_TO_PLACE_IDX, CELL_TO_PLACE_IDX)
# place node NODE_TO_PLACE_IDX @ position 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 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( 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)) 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( 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.GRID_ROW, self.GRID_COL), axis=0))
self.plc_util_os.place_node(NODE_TO_PLACE_IDX, CELL_TO_PLACE_IDX) self.plc_util_os.place_node(NODE_TO_PLACE_IDX, CELL_TO_PLACE_IDX)
...@@ -687,51 +845,68 @@ class PlacementCostTest(): ...@@ -687,51 +845,68 @@ class PlacementCostTest():
def test_environment(self): def test_environment(self):
print("############################ TEST ENVIRONMENT ############################") 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( env = environment.CircuitEnv(
_plc=plc_client, _plc=plc_client,
create_placement_cost_fn=placement_util.create_placement_cost, create_placement_cost_fn=placement_util.create_placement_cost,
netlist_file=self.NETLIST_PATH, netlist_file=self.NETLIST_PATH,
init_placement=self.PLC_PATH) init_placement=self.PLC_PATH,
unplace_all_nodes_in_init=False)
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=']')
env_os = environment.CircuitEnv( env_os = environment.CircuitEnv(
_plc=plc_client_os, _plc=plc_client_os,
create_placement_cost_fn=placement_util.create_placement_cost, create_placement_cost_fn=placement_util.create_placement_cost,
netlist_file=self.NETLIST_PATH, netlist_file=self.NETLIST_PATH,
init_placement=self.PLC_PATH) init_placement=self.PLC_PATH,
# print(np.array(env_os._plc.get_node_mask(13333)).reshape(33,35)) unplace_all_nodes_in_init=False)
# print(np.array(env._plc.get_node_mask(13333)).reshape(33,35))
# Init Mask
try:
assert (env_os._get_mask() == env._get_mask()).all() 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_gl = env._get_obs()
obs_os = env_os._get_obs() obs_os = env_os._get_obs()
env_os.reset() # env_os.reset()
env.reset() # env.reset()
for feature_gl, feature_os in zip(obs_gl, obs_os): for feature_gl, feature_os in zip(obs_gl, obs_os):
if not (obs_gl[feature_gl] == obs_os[feature_os]).all(): try:
print(feature_gl, feature_os) 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])) print(np.where(obs_gl[feature_gl] != obs_os[feature_os]))
exit(1)
print(" ++++++++++++++++++++++++++++++") print(" ++++++++++++++++++++++++++++++")
print(" +++ TEST ENVIRONMENT: PASS +++") print(" +++ TEST ENVIRONMENT: PASS +++")
...@@ -791,14 +966,18 @@ def main(args): ...@@ -791,14 +966,18 @@ def main(args):
marv=args.marv, marv=args.marv,
smooth=args.smooth) smooth=args.smooth)
"""
Uncomment any available tests
"""
# PCT.test_metadata() # PCT.test_metadata()
PCT.test_proxy_cost() PCT.test_proxy_cost()
# PCT.test_proxy_density()
# PCT.test_proxy_congestion()
# PCT.test_placement_util(keep_save_file=False) # PCT.test_placement_util(keep_save_file=False)
# PCT.test_place_node() # PCT.test_place_node()
# PCT.test_miscellaneous() # PCT.test_miscellaneous()
# PCT.test_observation_extractor() # PCT.test_observation_extractor()
# PCT.view_canvas() # PCT.view_canvas()
# PCT.test_proxy_congestion()
# PCT.test_environment() # 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