2022-10-20 22:46:17 +08:00
|
|
|
"""
|
|
|
|
|
Adapted from torch_geometric/data/data.py
|
|
|
|
|
"""
|
|
|
|
|
import torch
|
|
|
|
|
import collections
|
|
|
|
|
import copy
|
|
|
|
|
import math
|
|
|
|
|
from utils import *
|
|
|
|
|
|
|
|
|
|
def get_dataset(args, logger):
|
|
|
|
|
with open("./data/cad/%s/datalist.csv" % (args.dataset), "r") as f:
|
|
|
|
|
all_files = f.readlines()
|
|
|
|
|
all_files = [line[:-1] for line in all_files]
|
|
|
|
|
for cur_file in all_files:
|
|
|
|
|
yield cur_file
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_dataset(args, logger, placement=None):
|
|
|
|
|
rawdb, gpdb = None, None
|
2023-04-06 13:34:26 +08:00
|
|
|
params = get_single_design_params(
|
|
|
|
|
args.dataset_root, args.dataset, args.design_name, placement
|
|
|
|
|
)
|
|
|
|
|
parser = IOParser()
|
2022-10-20 22:46:17 +08:00
|
|
|
if args.load_from_raw:
|
|
|
|
|
logger.info("loading from original benchmark...")
|
|
|
|
|
rawdb, gpdb = parser.read(
|
|
|
|
|
params, verbose_log=False, lite_mode=True, random_place=False, num_threads=args.num_threads
|
|
|
|
|
)
|
|
|
|
|
design_info = parser.preprocess_design_info(gpdb)
|
|
|
|
|
else:
|
|
|
|
|
logger.info("loading from pt benchmark...")
|
|
|
|
|
design_pt_path = "./data/cad/%s/%s.pt" % (args.dataset, args.design_name)
|
2023-04-06 13:34:26 +08:00
|
|
|
parser.load_params(
|
|
|
|
|
params, verbose_log=False, lite_mode=True, random_place=False, num_threads=args.num_threads
|
|
|
|
|
)
|
2022-10-20 22:46:17 +08:00
|
|
|
design_info = torch.load(design_pt_path)
|
|
|
|
|
gpdb = None
|
|
|
|
|
data = PlaceData(args, logger, **design_info)
|
|
|
|
|
return data, rawdb, gpdb
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def size_repr(key, item, indent=0):
|
|
|
|
|
indent_str = " " * indent
|
|
|
|
|
if torch.is_tensor(item) and item.dim() == 0:
|
|
|
|
|
out = item.item()
|
|
|
|
|
elif torch.is_tensor(item):
|
|
|
|
|
out = str(list(item.size()))
|
|
|
|
|
elif isinstance(item, list) or isinstance(item, tuple):
|
|
|
|
|
out = str([len(item)])
|
|
|
|
|
elif isinstance(item, dict):
|
|
|
|
|
lines = [indent_str + size_repr(k, v, 2) for k, v in item.items()]
|
|
|
|
|
out = "{\n" + ",\n".join(lines) + "\n" + indent_str + "}"
|
|
|
|
|
elif isinstance(item, str):
|
|
|
|
|
out = f'"{item}"'
|
|
|
|
|
else:
|
|
|
|
|
out = str(item)
|
|
|
|
|
|
|
|
|
|
return f"{indent_str}{key}={out}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PlaceData(object):
|
|
|
|
|
def __init__(
|
|
|
|
|
self,
|
|
|
|
|
args,
|
|
|
|
|
logger,
|
|
|
|
|
node_pos=None,
|
2023-04-06 13:34:26 +08:00
|
|
|
node_lpos=None,
|
2022-10-20 22:46:17 +08:00
|
|
|
node_size=None,
|
|
|
|
|
pin_rel_cpos=None,
|
2023-04-06 13:34:26 +08:00
|
|
|
pin_rel_lpos=None,
|
2022-10-20 22:46:17 +08:00
|
|
|
pin_size=None,
|
|
|
|
|
pin_id2node_id=None,
|
|
|
|
|
hyperedge_index=None,
|
|
|
|
|
hyperedge_list=None,
|
|
|
|
|
hyperedge_list_end=None,
|
2023-04-06 13:34:26 +08:00
|
|
|
node2pin_index=None,
|
|
|
|
|
node2pin_list=None,
|
|
|
|
|
node2pin_list_end=None,
|
2022-10-20 22:46:17 +08:00
|
|
|
node_id2region_id=None,
|
|
|
|
|
region_boxes=None,
|
|
|
|
|
region_boxes_end=None,
|
|
|
|
|
dataset_path=None,
|
|
|
|
|
benchmark=None,
|
|
|
|
|
die_info=None,
|
|
|
|
|
site_info=None,
|
|
|
|
|
node_type_indices=None,
|
|
|
|
|
node_id2node_name=None,
|
|
|
|
|
movable_index=None,
|
|
|
|
|
connected_index=None,
|
|
|
|
|
fixed_index=None,
|
|
|
|
|
**kwargs,
|
|
|
|
|
):
|
|
|
|
|
self.die_info = die_info # lx, hx, ly, hy
|
|
|
|
|
self.die_ur = None
|
|
|
|
|
self.die_ll = None
|
|
|
|
|
|
|
|
|
|
self.node_pos = node_pos
|
2023-04-06 13:34:26 +08:00
|
|
|
self.node_lpos = node_lpos
|
2022-10-20 22:46:17 +08:00
|
|
|
self.node_size = node_size
|
|
|
|
|
|
|
|
|
|
self.pin_rel_cpos = pin_rel_cpos
|
2023-04-06 13:34:26 +08:00
|
|
|
self.pin_rel_lpos = pin_rel_lpos
|
2022-10-20 22:46:17 +08:00
|
|
|
self.pin_size = pin_size
|
|
|
|
|
|
|
|
|
|
self.pin_id2node_id = pin_id2node_id
|
|
|
|
|
|
|
|
|
|
self.hyperedge_index = hyperedge_index
|
|
|
|
|
self.hyperedge_list = hyperedge_list
|
|
|
|
|
self.hyperedge_list_end = hyperedge_list_end
|
|
|
|
|
self.pin_id2net_id = hyperedge_index[1]
|
|
|
|
|
|
2023-04-06 13:34:26 +08:00
|
|
|
self.node2pin_index = node2pin_index
|
|
|
|
|
self.node2pin_list = node2pin_list
|
|
|
|
|
self.node2pin_list_end = node2pin_list_end
|
|
|
|
|
|
2022-10-20 22:46:17 +08:00
|
|
|
self.node_id2region_id = node_id2region_id
|
|
|
|
|
self.region_boxes = region_boxes
|
|
|
|
|
self.region_boxes_end = region_boxes_end
|
|
|
|
|
|
|
|
|
|
dataset_format = ""
|
|
|
|
|
if "aux" in dataset_path.keys():
|
|
|
|
|
dataset_format = "bookshelf"
|
|
|
|
|
elif "def" in dataset_path.keys():
|
|
|
|
|
dataset_format = "lefdef"
|
|
|
|
|
self.__dataset_format__ = dataset_format
|
|
|
|
|
self.__dataset_path__ = dataset_path
|
|
|
|
|
self.__design_name__ = benchmark + "/" + dataset_path["design_name"]
|
|
|
|
|
|
|
|
|
|
self.__node_id2node_name__ = node_id2node_name
|
|
|
|
|
|
|
|
|
|
# NOTE: we set float movable node as connected node for convenience purposes
|
|
|
|
|
self.__node_type_indices__ = node_type_indices
|
|
|
|
|
self.__movable_index__ = movable_index
|
|
|
|
|
self.__movable_connected_index__ = (
|
|
|
|
|
movable_index[0],
|
|
|
|
|
self.node_type_indices[0][1],
|
|
|
|
|
)
|
|
|
|
|
self.__connected_index__ = connected_index
|
|
|
|
|
self.__fixed_index__ = fixed_index
|
|
|
|
|
self.__fixed_connected_index__ = (self.fixed_index[0], self.connected_index[1])
|
|
|
|
|
self.__fixed_unconnected_index__ = (
|
|
|
|
|
self.connected_index[1],
|
|
|
|
|
self.fixed_index[1],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.__site_width__ = site_info[0]
|
|
|
|
|
self.__site_height__ = site_info[1]
|
2023-04-06 13:34:26 +08:00
|
|
|
self.__row_height__ = site_info[1] # the same as site height
|
2022-10-20 22:46:17 +08:00
|
|
|
|
|
|
|
|
self.__ori_die_lx__ = die_info[0].item()
|
|
|
|
|
self.__ori_die_hx__ = die_info[1].item()
|
|
|
|
|
self.__ori_die_ly__ = die_info[2].item()
|
|
|
|
|
self.__ori_die_hy__ = die_info[3].item()
|
|
|
|
|
|
|
|
|
|
self.__num_nodes__ = node_pos.shape[0]
|
|
|
|
|
self.__num_pins__ = pin_id2node_id.shape[0]
|
|
|
|
|
self.__num_nets__ = hyperedge_list_end.shape[0]
|
|
|
|
|
|
|
|
|
|
self.__num_bin_x__ = args.num_bin_x
|
|
|
|
|
self.__num_bin_y__ = args.num_bin_y
|
|
|
|
|
|
|
|
|
|
self.__clamp_node__ = args.clamp_node
|
|
|
|
|
|
|
|
|
|
# fence region
|
|
|
|
|
self.__num_regions__ = 1
|
|
|
|
|
self.__enable_fence__ = False
|
|
|
|
|
|
|
|
|
|
# Extra variable to handle corner cases
|
|
|
|
|
self.fix_node_in_bd_mask = None
|
|
|
|
|
self.dummy_macro_pos = None
|
|
|
|
|
self.dummy_macro_size = None
|
2023-04-06 13:34:26 +08:00
|
|
|
self.mov_node_size_real = None
|
2022-10-20 22:46:17 +08:00
|
|
|
|
|
|
|
|
# filler
|
|
|
|
|
self.filler_size = None
|
|
|
|
|
|
|
|
|
|
self.__logger__ = logger
|
|
|
|
|
self.__args__ = args
|
|
|
|
|
|
|
|
|
|
for key, item in kwargs.items():
|
|
|
|
|
self[key] = item
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def dataset_format(self):
|
|
|
|
|
if hasattr(self, "__dataset_format__"):
|
|
|
|
|
return self.__dataset_format__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def dataset_path(self):
|
|
|
|
|
if hasattr(self, "__dataset_path__"):
|
|
|
|
|
return self.__dataset_path__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def design_name(self):
|
|
|
|
|
if hasattr(self, "__design_name__"):
|
|
|
|
|
return self.__design_name__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def node_id2node_name(self):
|
|
|
|
|
if hasattr(self, "__node_id2node_name__"):
|
|
|
|
|
return self.__node_id2node_name__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def node_type_indices(self):
|
|
|
|
|
if hasattr(self, "__node_type_indices__"):
|
|
|
|
|
return self.__node_type_indices__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def movable_index(self):
|
|
|
|
|
if hasattr(self, "__movable_index__"):
|
|
|
|
|
return self.__movable_index__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def movable_connected_index(self):
|
|
|
|
|
if hasattr(self, "__movable_connected_index__"):
|
|
|
|
|
return self.__movable_connected_index__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def connected_index(self):
|
|
|
|
|
if hasattr(self, "__connected_index__"):
|
|
|
|
|
return self.__connected_index__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def fixed_index(self):
|
|
|
|
|
if hasattr(self, "__fixed_index__"):
|
|
|
|
|
return self.__fixed_index__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def fixed_connected_index(self):
|
|
|
|
|
if hasattr(self, "__fixed_connected_index__"):
|
|
|
|
|
return self.__fixed_connected_index__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def fixed_unconnected_index(self):
|
|
|
|
|
if hasattr(self, "__fixed_unconnected_index__"):
|
|
|
|
|
return self.__fixed_unconnected_index__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def site_width(self):
|
|
|
|
|
if hasattr(self, "__site_width__"):
|
|
|
|
|
return self.__site_width__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def site_height(self):
|
|
|
|
|
if hasattr(self, "__site_height__"):
|
|
|
|
|
return self.__site_height__
|
|
|
|
|
|
2023-04-06 13:34:26 +08:00
|
|
|
@property
|
|
|
|
|
def row_height(self):
|
|
|
|
|
if hasattr(self, "__row_height__"):
|
|
|
|
|
return self.__row_height__
|
|
|
|
|
|
2022-10-20 22:46:17 +08:00
|
|
|
@property
|
|
|
|
|
def ori_die_lx(self):
|
|
|
|
|
if hasattr(self, "__ori_die_lx__"):
|
|
|
|
|
return self.__ori_die_lx__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def ori_die_hx(self):
|
|
|
|
|
if hasattr(self, "__ori_die_hx__"):
|
|
|
|
|
return self.__ori_die_hx__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def ori_die_ly(self):
|
|
|
|
|
if hasattr(self, "__ori_die_ly__"):
|
|
|
|
|
return self.__ori_die_ly__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def ori_die_hy(self):
|
|
|
|
|
if hasattr(self, "__ori_die_hy__"):
|
|
|
|
|
return self.__ori_die_hy__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def die_shift(self):
|
|
|
|
|
if hasattr(self, "__die_shift__"):
|
|
|
|
|
return self.__die_shift__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def die_scale(self):
|
|
|
|
|
if hasattr(self, "__die_scale__"):
|
|
|
|
|
return self.__die_scale__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def num_nodes(self):
|
|
|
|
|
if hasattr(self, "__num_nodes__"):
|
|
|
|
|
return self.__num_nodes__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def num_pins(self):
|
|
|
|
|
if hasattr(self, "__num_pins__"):
|
|
|
|
|
return self.__num_pins__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def num_nets(self):
|
|
|
|
|
if hasattr(self, "__num_nets__"):
|
|
|
|
|
return self.__num_nets__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def num_fillers(self):
|
|
|
|
|
if hasattr(self, "__num_fillers__"):
|
|
|
|
|
return self.__num_fillers__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def num_bin_x(self):
|
|
|
|
|
if hasattr(self, "__num_bin_x__"):
|
|
|
|
|
return self.__num_bin_x__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def num_bin_y(self):
|
|
|
|
|
if hasattr(self, "__num_bin_y__"):
|
|
|
|
|
return self.__num_bin_y__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def clamp_node(self):
|
|
|
|
|
if hasattr(self, "__clamp_node__"):
|
|
|
|
|
return self.__clamp_node__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def enable_fence(self):
|
|
|
|
|
if hasattr(self, "__enable_fence__"):
|
|
|
|
|
return self.__enable_fence__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def num_regions(self):
|
|
|
|
|
if hasattr(self, "__num_regions__"):
|
|
|
|
|
return self.__num_regions__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def total_mov_area_without_filler(self):
|
|
|
|
|
if hasattr(self, "__total_mov_area_without_filler__"):
|
|
|
|
|
return self.__total_mov_area_without_filler__
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def bin_area(self):
|
|
|
|
|
if hasattr(self, "__bin_area__"):
|
|
|
|
|
return self.__bin_area__
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def from_dict(cls, dictionary):
|
|
|
|
|
r"""Creates a data object from a python dictionary."""
|
|
|
|
|
data = cls()
|
|
|
|
|
|
|
|
|
|
for key, item in dictionary.items():
|
|
|
|
|
data[key] = item
|
|
|
|
|
|
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
|
return {key: item for key, item in self}
|
|
|
|
|
|
|
|
|
|
def to_namedtuple(self):
|
|
|
|
|
keys = self.keys
|
|
|
|
|
DataTuple = collections.namedtuple("DataTuple", keys)
|
|
|
|
|
return DataTuple(*[self[key] for key in keys])
|
|
|
|
|
|
|
|
|
|
def __getitem__(self, key):
|
|
|
|
|
r"""Gets the data of the attribute :obj:`key`."""
|
|
|
|
|
return getattr(self, key, None)
|
|
|
|
|
|
|
|
|
|
def __setitem__(self, key, value):
|
|
|
|
|
"""Sets the attribute :obj:`key` to :obj:`value`."""
|
|
|
|
|
setattr(self, key, value)
|
|
|
|
|
|
|
|
|
|
def __delitem__(self, key):
|
|
|
|
|
r"""Delete the data of the attribute :obj:`key`."""
|
|
|
|
|
return delattr(self, key)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def keys(self):
|
|
|
|
|
r"""Returns all names of graph attributes."""
|
|
|
|
|
keys = [key for key in self.__dict__.keys() if self[key] is not None]
|
|
|
|
|
keys = [key for key in keys if key[:2] != "__" and key[-2:] != "__"]
|
|
|
|
|
return keys
|
|
|
|
|
|
|
|
|
|
def __len__(self):
|
|
|
|
|
r"""Returns the number of all present attributes."""
|
|
|
|
|
return len(self.keys)
|
|
|
|
|
|
|
|
|
|
def __contains__(self, key):
|
|
|
|
|
r"""Returns :obj:`True`, if the attribute :obj:`key` is present in the
|
|
|
|
|
data."""
|
|
|
|
|
return key in self.keys
|
|
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
|
r"""Iterates over all present attributes in the data, yielding their
|
|
|
|
|
attribute names and content."""
|
|
|
|
|
for key in sorted(self.keys):
|
|
|
|
|
yield key, self[key]
|
|
|
|
|
|
|
|
|
|
def __call__(self, *keys):
|
|
|
|
|
r"""Iterates over all attributes :obj:`*keys` in the data, yielding
|
|
|
|
|
their attribute names and content.
|
|
|
|
|
If :obj:`*keys` is not given this method will iterative over all
|
|
|
|
|
present attributes."""
|
|
|
|
|
for key in sorted(self.keys) if not keys else keys:
|
|
|
|
|
if key in self:
|
|
|
|
|
yield key, self[key]
|
|
|
|
|
|
|
|
|
|
def __apply__(self, item, func):
|
|
|
|
|
if torch.is_tensor(item):
|
|
|
|
|
return func(item)
|
|
|
|
|
elif isinstance(item, (tuple, list)):
|
|
|
|
|
return [self.__apply__(v, func) for v in item]
|
|
|
|
|
elif isinstance(item, dict):
|
|
|
|
|
return {k: self.__apply__(v, func) for k, v in item.items()}
|
|
|
|
|
else:
|
|
|
|
|
return item
|
|
|
|
|
|
|
|
|
|
def apply(self, func, *keys):
|
|
|
|
|
r"""Applies the function :obj:`func` to all tensor attributes
|
|
|
|
|
:obj:`*keys`. If :obj:`*keys` is not given, :obj:`func` is applied to
|
|
|
|
|
all present attributes.
|
|
|
|
|
"""
|
|
|
|
|
for key, item in self(*keys):
|
|
|
|
|
self[key] = self.__apply__(item, func)
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def contiguous(self, *keys):
|
|
|
|
|
r"""Ensures a contiguous memory layout for all attributes :obj:`*keys`.
|
|
|
|
|
If :obj:`*keys` is not given, all present attributes are ensured to
|
|
|
|
|
have a contiguous memory layout."""
|
|
|
|
|
return self.apply(lambda x: x.contiguous(), *keys)
|
|
|
|
|
|
|
|
|
|
def to(self, device, *keys, **kwargs):
|
|
|
|
|
r"""Performs tensor dtype and/or device conversion to all attributes
|
|
|
|
|
:obj:`*keys`.
|
|
|
|
|
If :obj:`*keys` is not given, the conversion is applied to all present
|
|
|
|
|
attributes."""
|
|
|
|
|
return self.apply(lambda x: x.to(device, **kwargs), *keys)
|
|
|
|
|
|
|
|
|
|
def cpu(self, *keys):
|
|
|
|
|
r"""Copies all attributes :obj:`*keys` to CPU memory.
|
|
|
|
|
If :obj:`*keys` is not given, the conversion is applied to all present
|
|
|
|
|
attributes."""
|
|
|
|
|
return self.apply(lambda x: x.cpu(), *keys)
|
|
|
|
|
|
|
|
|
|
def cuda(self, device=None, non_blocking=False, *keys):
|
|
|
|
|
r"""Copies all attributes :obj:`*keys` to CUDA memory.
|
|
|
|
|
If :obj:`*keys` is not given, the conversion is applied to all present
|
|
|
|
|
attributes."""
|
|
|
|
|
return self.apply(
|
|
|
|
|
lambda x: x.cuda(device=device, non_blocking=non_blocking), *keys
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def clone(self):
|
|
|
|
|
return self.__class__.from_dict(
|
|
|
|
|
{
|
|
|
|
|
k: v.clone() if torch.is_tensor(v) else copy.deepcopy(v)
|
|
|
|
|
for k, v in self.__dict__.items()
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def pin_memory(self, *keys):
|
|
|
|
|
r"""Copies all attributes :obj:`*keys` to pinned memory.
|
|
|
|
|
If :obj:`*keys` is not given, the conversion is applied to all present
|
|
|
|
|
attributes."""
|
|
|
|
|
return self.apply(lambda x: x.pin_memory(), *keys)
|
|
|
|
|
|
|
|
|
|
def record_stream(self, stream: torch.cuda.Stream, *keys):
|
|
|
|
|
r"""Ensures that the tensor memory is not reused for another tensor
|
|
|
|
|
until all current work queued on :obj:`stream` has been completed.
|
|
|
|
|
If :obj:`*keys` is not given, this will be ensured for all present
|
|
|
|
|
attributes."""
|
|
|
|
|
|
|
|
|
|
def _record_stream(x):
|
|
|
|
|
x.record_stream(stream)
|
|
|
|
|
return x
|
|
|
|
|
|
|
|
|
|
return self.apply(_record_stream, *keys)
|
|
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
|
cls = str(self.__class__.__name__)
|
|
|
|
|
has_dict = any([isinstance(item, dict) for _, item in self])
|
|
|
|
|
|
|
|
|
|
if not has_dict:
|
|
|
|
|
info = [size_repr(key, item) for key, item in self]
|
|
|
|
|
return "{}({}, {})".format(cls, self.design_name, ", ".join(info))
|
|
|
|
|
else:
|
|
|
|
|
info = [size_repr(key, item, indent=2) for key, item in self]
|
|
|
|
|
return "{}({}, \n{}\n)".format(cls, self.design_name, ",\n".join(info))
|
|
|
|
|
|
|
|
|
|
def backup_ori_var(self):
|
|
|
|
|
# backup original position and size
|
|
|
|
|
self.__ori_die_info__ = self.die_info.clone().cpu().numpy()
|
|
|
|
|
self.__ori_node_pos__ = self.node_pos.clone().cpu().numpy()
|
2023-04-06 13:34:26 +08:00
|
|
|
self.__ori_node_lpos__ = self.node_lpos.clone().cpu().numpy()
|
2022-10-20 22:46:17 +08:00
|
|
|
self.__ori_node_size__ = self.node_size.clone().cpu().numpy()
|
|
|
|
|
self.__ori_pin_rel_cpos__ = self.pin_rel_cpos.clone().cpu().numpy()
|
2023-04-06 13:34:26 +08:00
|
|
|
self.__ori_pin_rel_lpos__ = self.pin_rel_lpos.clone().cpu().numpy()
|
2022-10-20 22:46:17 +08:00
|
|
|
self.__ori_pin_size__ = self.pin_size.clone().cpu().numpy()
|
|
|
|
|
self.__ori_region_boxes__ = self.region_boxes.clone().cpu().numpy()
|
|
|
|
|
dtype, device = self.die_info.dtype, self.die_info.device
|
|
|
|
|
self.__die_shift__ = torch.tensor([0.0, 0.0], dtype=dtype, device=device)
|
|
|
|
|
self.__die_scale__ = torch.tensor([1.0, 1.0], dtype=dtype, device=device)
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def preshift(self):
|
|
|
|
|
# shift die info to (0.0, hx, 0.0, hy)
|
|
|
|
|
die_lx, _, die_ly, _ = self.die_info.tolist()
|
|
|
|
|
die_shift = torch.tensor(
|
|
|
|
|
[die_lx, die_ly], dtype=self.die_info.dtype, device=self.die_info.device,
|
|
|
|
|
)
|
|
|
|
|
self.die_info = (self.die_info.reshape(2, 2).t() - die_shift).t().reshape(-1)
|
|
|
|
|
self.region_boxes = (
|
|
|
|
|
(self.region_boxes.reshape(-1, 2, 2).permute(0, 2, 1) - die_shift)
|
|
|
|
|
.permute(0, 2, 1)
|
|
|
|
|
.reshape(-1, 4)
|
|
|
|
|
)
|
|
|
|
|
self.node_pos -= die_shift
|
2023-04-06 13:34:26 +08:00
|
|
|
self.node_lpos -= die_shift
|
2022-10-20 22:46:17 +08:00
|
|
|
self.__die_shift__ += die_shift
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def prescale_by_site_width(self):
|
|
|
|
|
# inplace scaling
|
|
|
|
|
self.die_info /= self.site_width
|
|
|
|
|
self.region_boxes /= self.site_width
|
|
|
|
|
self.node_pos /= self.site_width
|
2023-04-06 13:34:26 +08:00
|
|
|
self.node_lpos /= self.site_width
|
2022-10-20 22:46:17 +08:00
|
|
|
self.node_size /= self.site_width
|
|
|
|
|
self.pin_rel_cpos /= self.site_width
|
2023-04-06 13:34:26 +08:00
|
|
|
self.pin_rel_lpos /= self.site_width
|
2022-10-20 22:46:17 +08:00
|
|
|
self.pin_size /= self.site_width
|
|
|
|
|
self.__die_scale__ *= self.site_width
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def prescale(self):
|
|
|
|
|
# scale die info to (0.0, 1.0, 0.0, 1.0)
|
|
|
|
|
die_lx, die_hx, die_ly, die_hy = self.die_info.tolist()
|
|
|
|
|
die_scale = torch.tensor(
|
|
|
|
|
[die_hx - die_lx, die_hy - die_ly],
|
|
|
|
|
dtype=self.die_info.dtype,
|
|
|
|
|
device=self.die_info.device,
|
|
|
|
|
)
|
|
|
|
|
self.node_pos /= die_scale
|
2023-04-06 13:34:26 +08:00
|
|
|
self.node_lpos /= die_scale
|
2022-10-20 22:46:17 +08:00
|
|
|
self.node_size /= die_scale
|
|
|
|
|
self.pin_rel_cpos /= die_scale
|
2023-04-06 13:34:26 +08:00
|
|
|
self.pin_rel_lpos /= die_scale
|
2022-10-20 22:46:17 +08:00
|
|
|
self.pin_size /= die_scale
|
|
|
|
|
self.die_info = (self.die_info.reshape(2, 2).t() / die_scale).t().reshape(-1)
|
|
|
|
|
self.region_boxes = (
|
|
|
|
|
(self.region_boxes.reshape(-1, 2, 2).permute(0, 2, 1) / die_scale)
|
|
|
|
|
.permute(0, 2, 1)
|
|
|
|
|
.reshape(-1, 4)
|
|
|
|
|
)
|
|
|
|
|
self.__die_scale__ *= die_scale
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def pre_compute_var(self):
|
|
|
|
|
args = self.__args__
|
|
|
|
|
device = self.node_size.get_device()
|
|
|
|
|
# die related
|
|
|
|
|
lx, hx, ly, hy = self.die_info.tolist()
|
|
|
|
|
self.unit_len = torch.tensor(
|
|
|
|
|
[(hx - lx) / self.num_bin_x, (hy - ly) / self.num_bin_y], device=device
|
|
|
|
|
)
|
|
|
|
|
self.die_ur = self.die_info.reshape(2, 2).t()[1].clone()
|
|
|
|
|
self.die_ll = self.die_info.reshape(2, 2).t()[0].clone()
|
|
|
|
|
self.hpwl_scale = self.die_scale / self.site_width
|
|
|
|
|
# node related
|
|
|
|
|
self.node_area = torch.prod(self.node_size, 1).unsqueeze(1)
|
|
|
|
|
self.node_to_num_pins = torch.zeros(self.num_nodes, device=device)
|
|
|
|
|
v = torch.ones(self.pin_id2node_id.shape[0], device=device)
|
2023-04-06 13:34:26 +08:00
|
|
|
self.node_to_num_pins.scatter_add_(0, self.pin_id2node_id, v).round_()
|
2022-10-20 22:46:17 +08:00
|
|
|
self.node_to_num_pins.unsqueeze_(1)
|
|
|
|
|
# net related
|
|
|
|
|
start_idx = self.hyperedge_list_end.roll(1)
|
|
|
|
|
start_idx[0] = 0
|
|
|
|
|
self.net_to_num_pins = self.hyperedge_list_end - start_idx
|
|
|
|
|
self.net_mask = torch.logical_and(
|
|
|
|
|
self.net_to_num_pins <= args.ignore_net_degree, self.net_to_num_pins >= 2
|
|
|
|
|
) # 0: ignore, 1: consider in wirelength calculation
|
|
|
|
|
# obj related
|
|
|
|
|
mov_lhs, mov_rhs = self.movable_index
|
|
|
|
|
mov_cell_area = torch.prod(self.node_size[mov_lhs:mov_rhs, ...], 1)
|
|
|
|
|
self.__total_mov_area_without_filler__ = torch.sum(mov_cell_area).item()
|
|
|
|
|
self.__bin_area__ = torch.prod(self.unit_len).item()
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def init_fence_region(self):
|
|
|
|
|
offset = self.region_boxes_end.diff().tolist()
|
|
|
|
|
regions = torch.split(self.region_boxes[1:], offset)
|
|
|
|
|
self.regions = (
|
|
|
|
|
self.region_boxes[0],
|
|
|
|
|
*regions,
|
|
|
|
|
) # first region is the default region (core area)
|
|
|
|
|
self.__num_regions__ = len(self.regions)
|
|
|
|
|
self.__enable_fence__ = len(self.regions) > 1
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def compute_filler(self, args, logger):
|
|
|
|
|
if self.enable_fence:
|
|
|
|
|
return self.compute_filler_with_fence(args, logger)
|
|
|
|
|
else:
|
|
|
|
|
return self.compute_filler_without_fence(args, logger)
|
|
|
|
|
|
|
|
|
|
def compute_filler_with_fence(self, args, logger):
|
|
|
|
|
raise NotImplementedError("We haven't yet supported fence region.")
|
|
|
|
|
|
|
|
|
|
def compute_filler_without_fence(self, args, logger):
|
|
|
|
|
self.__num_fillers__ = 0
|
|
|
|
|
if args.use_filler:
|
|
|
|
|
mov_lhs, mov_rhs = self.movable_index
|
|
|
|
|
mov_node_size = self.node_size[mov_lhs:mov_rhs, ...]
|
|
|
|
|
die_area = torch.prod(self.die_ur - self.die_ll)
|
|
|
|
|
# init_density_map already multiplies with args.target_density,
|
|
|
|
|
# we need to divide it back
|
|
|
|
|
ori_dmap = (self.init_density_map / args.target_density).sum()
|
|
|
|
|
# init_density_map are all normalized to (0.0, 1.0)
|
|
|
|
|
fixed_node_area = ori_dmap * self.bin_area
|
|
|
|
|
placeable_area = die_area - fixed_node_area
|
|
|
|
|
if True:
|
|
|
|
|
mov_cell_area = torch.prod(mov_node_size, 1)
|
|
|
|
|
num_movable_nodes = mov_rhs - mov_lhs
|
|
|
|
|
mov_node_xsize_order = torch.argsort(mov_node_size[:, 0])
|
|
|
|
|
filler_size_x = torch.mean(
|
|
|
|
|
mov_node_size[:, 0][
|
|
|
|
|
mov_node_xsize_order[
|
|
|
|
|
int(num_movable_nodes * 0.05) : int(
|
|
|
|
|
num_movable_nodes * 0.95
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
filler_size_y = self.site_height / self.die_scale[1]
|
|
|
|
|
total_filler_area = max(
|
|
|
|
|
args.target_density * placeable_area - torch.sum(mov_cell_area),
|
|
|
|
|
0.0,
|
|
|
|
|
)
|
|
|
|
|
single_filler_size = torch.tensor(
|
|
|
|
|
[filler_size_x, filler_size_y],
|
|
|
|
|
device=mov_node_size.device,
|
|
|
|
|
dtype=mov_node_size.dtype,
|
|
|
|
|
)
|
|
|
|
|
self.__num_fillers__ = int(
|
|
|
|
|
torch.round(total_filler_area / (filler_size_x * filler_size_y))
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
mov_cell_area = torch.prod(mov_node_size, 1)
|
|
|
|
|
total_filler_area = max(
|
|
|
|
|
args.target_density * placeable_area
|
|
|
|
|
- torch.sum(mov_cell_area).item(),
|
|
|
|
|
0.0,
|
|
|
|
|
)
|
|
|
|
|
single_filler_area = torch.mean(mov_cell_area)
|
|
|
|
|
single_filler_size = single_filler_area.sqrt().repeat(2)
|
|
|
|
|
self.__num_fillers__ = int(total_filler_area / single_filler_area)
|
|
|
|
|
if self.num_fillers > 0:
|
|
|
|
|
self.filler_size = single_filler_size.repeat(self.num_fillers, 1)
|
|
|
|
|
logger.info(
|
|
|
|
|
"#Fillers: %d Filler size: (%.4e, %.4e)"
|
|
|
|
|
% (
|
|
|
|
|
self.num_fillers,
|
|
|
|
|
single_filler_size[0].item(),
|
|
|
|
|
single_filler_size[1].item(),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
logger.warning(
|
|
|
|
|
"num_fillers[%d] is smaller or equal to 0. Please make sure target_density[%.2f]"
|
|
|
|
|
" is larger than movable cell utilization[%.2f]. use_filler is disable."
|
|
|
|
|
% (
|
|
|
|
|
self.num_fillers,
|
|
|
|
|
args.target_density,
|
|
|
|
|
torch.sum(torch.prod(mov_node_size, 1)),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
args.use_filler = False
|
|
|
|
|
|
2023-04-06 13:34:26 +08:00
|
|
|
die_area, placeable_area = die_area.item(), placeable_area.item()
|
|
|
|
|
fixed_node_area, mov_cell_area = fixed_node_area.item(), torch.sum(mov_cell_area).item()
|
|
|
|
|
total_filler_area = float(total_filler_area)
|
|
|
|
|
logger.info(
|
|
|
|
|
"DieArea: %.3E FixArea: %.3E (%.1f%%) PlaceableArea: %.3E (%.1f%%) MovArea: %.3E (%.1f%%) FillerArea: %.3E (%.1f%%)"
|
|
|
|
|
% (
|
|
|
|
|
die_area,
|
|
|
|
|
fixed_node_area, fixed_node_area / die_area * 100,
|
|
|
|
|
placeable_area, placeable_area / die_area * 100,
|
|
|
|
|
mov_cell_area, mov_cell_area / die_area * 100,
|
|
|
|
|
total_filler_area, total_filler_area / die_area * 100,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2022-10-20 22:46:17 +08:00
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def compute_precond_var(self):
|
|
|
|
|
mov_lhs, mov_rhs = self.movable_index
|
|
|
|
|
self.mov_node_area = self.node_area[mov_lhs:mov_rhs]
|
|
|
|
|
self.mov_node_to_num_pins = self.node_to_num_pins[mov_lhs:mov_rhs]
|
|
|
|
|
if self.filler_size is not None:
|
|
|
|
|
num_fillers = self.filler_size.shape[0]
|
|
|
|
|
filler_area = torch.prod(self.filler_size, 1).unsqueeze(1)
|
|
|
|
|
self.mov_node_area = torch.cat((self.mov_node_area, filler_area), dim=0)
|
|
|
|
|
filler_to_num_pins = self.mov_node_to_num_pins.new_zeros((num_fillers, 1))
|
|
|
|
|
assert filler_to_num_pins.shape == filler_area.shape
|
|
|
|
|
self.mov_node_to_num_pins = torch.cat(
|
|
|
|
|
(self.mov_node_to_num_pins, filler_to_num_pins), dim=0
|
|
|
|
|
)
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def compute_sorted_node_map(self):
|
|
|
|
|
_, mov_sorted_map = torch.sort(self.mov_node_area.flatten(), descending=True)
|
|
|
|
|
mov_sorted_map = mov_sorted_map.contiguous()
|
|
|
|
|
mov_conn_sorted_map = mov_sorted_map
|
|
|
|
|
filler_sorted_map = None
|
|
|
|
|
if self.filler_size is not None:
|
|
|
|
|
mov_lhs, mov_rhs = self.movable_index
|
|
|
|
|
_, mov_conn_sorted_map = torch.sort(
|
|
|
|
|
self.mov_node_area[mov_lhs:mov_rhs].flatten(), descending=True
|
|
|
|
|
)
|
|
|
|
|
_, filler_sorted_map = torch.sort(
|
|
|
|
|
self.mov_node_area[mov_rhs:].flatten(), descending=True
|
|
|
|
|
)
|
|
|
|
|
mov_conn_sorted_map = mov_conn_sorted_map.contiguous()
|
|
|
|
|
filler_sorted_map = filler_sorted_map.contiguous()
|
|
|
|
|
self.sorted_maps = (mov_sorted_map, mov_conn_sorted_map, filler_sorted_map)
|
|
|
|
|
|
|
|
|
|
def logging_statistics(self):
|
|
|
|
|
args = self.__args__
|
|
|
|
|
logger = self.__logger__
|
|
|
|
|
content = "\n===================\n"
|
|
|
|
|
content += "#nodes = %d, #nets = %d, #pins = %d\n" % (
|
|
|
|
|
self.num_nodes,
|
|
|
|
|
self.num_nets,
|
|
|
|
|
self.num_pins,
|
|
|
|
|
)
|
|
|
|
|
num_conmov_nodes = self.node_type_indices[0][1] - self.node_type_indices[0][0]
|
|
|
|
|
num_fltmov_nodes = self.node_type_indices[1][1] - self.node_type_indices[1][0]
|
|
|
|
|
num_confix_nodes = self.node_type_indices[2][1] - self.node_type_indices[2][0]
|
|
|
|
|
num_fltfix_nodes = self.node_type_indices[6][1] - self.node_type_indices[6][0]
|
|
|
|
|
num_coniopin = self.node_type_indices[3][1] - self.node_type_indices[3][0]
|
|
|
|
|
num_fltiopin = self.node_type_indices[5][1] - self.node_type_indices[5][0]
|
|
|
|
|
num_blkg = self.node_type_indices[4][1] - self.node_type_indices[4][0]
|
|
|
|
|
content += "#Mov = %d, #Fix = %d, #IOPin = %d, #Blkg = %d\n" % (
|
|
|
|
|
num_conmov_nodes + num_fltmov_nodes,
|
|
|
|
|
num_confix_nodes + num_fltfix_nodes,
|
|
|
|
|
num_coniopin + num_fltiopin,
|
|
|
|
|
num_blkg,
|
|
|
|
|
)
|
|
|
|
|
content += (
|
|
|
|
|
"#ConnMov = %d, #FloatMov = %d, #ConnFix = %d, #FloatFix = %d, #ConnIOPin = %d, #FloatIOPin = %d\n"
|
|
|
|
|
% (
|
|
|
|
|
num_conmov_nodes,
|
|
|
|
|
num_fltmov_nodes,
|
|
|
|
|
num_confix_nodes,
|
|
|
|
|
num_fltfix_nodes,
|
|
|
|
|
num_coniopin,
|
|
|
|
|
num_fltiopin,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
content += "Core Info " + str(self.die_info.tolist()) + "\n"
|
|
|
|
|
content += "Site Width = %d, Row Height = %d\n" % (
|
|
|
|
|
self.site_width,
|
|
|
|
|
self.site_height,
|
|
|
|
|
)
|
|
|
|
|
content += "#Bins = (%d, %d), UnitLen = (%.5f, %.5f)\n" % (
|
|
|
|
|
self.num_bin_x,
|
|
|
|
|
self.num_bin_y,
|
|
|
|
|
self.unit_len[0],
|
|
|
|
|
self.unit_len[1],
|
|
|
|
|
)
|
|
|
|
|
content += "target density = %.2f\n" % (args.target_density)
|
|
|
|
|
content += "==================="
|
|
|
|
|
logger.info(content)
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def preprocess(self):
|
|
|
|
|
args = self.__args__
|
|
|
|
|
self.backup_ori_var()
|
|
|
|
|
self.preshift()
|
|
|
|
|
self.prescale_by_site_width()
|
|
|
|
|
if args.scale_design:
|
|
|
|
|
self.prescale()
|
|
|
|
|
self.pre_compute_var()
|
|
|
|
|
self.init_fence_region()
|
|
|
|
|
self.logging_statistics()
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def init_filler(self):
|
|
|
|
|
self.compute_filler(self.__args__, self.__logger__)
|
|
|
|
|
self.compute_precond_var()
|
|
|
|
|
self.compute_sorted_node_map()
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def get_mov_node_info(self, init_method="randn_center"):
|
|
|
|
|
args = self.__args__
|
|
|
|
|
mov_lhs, mov_rhs = self.movable_index
|
2023-04-06 13:34:26 +08:00
|
|
|
mov_node_pos = self.node_pos[mov_lhs:mov_rhs, ...].clone()
|
|
|
|
|
mov_node_size = self.node_size[mov_lhs:mov_rhs, ...].clone()
|
2022-10-20 22:46:17 +08:00
|
|
|
|
|
|
|
|
if init_method == "randn_center":
|
|
|
|
|
scale = (self.die_ur - self.die_ll) * 0.001
|
|
|
|
|
loc = (self.die_ur + self.die_ll) * 0.5
|
|
|
|
|
mov_node_pos = torch.randn_like(mov_node_pos) * scale + loc
|
|
|
|
|
|
|
|
|
|
if self.num_fillers > 0:
|
|
|
|
|
if self.enable_fence:
|
|
|
|
|
raise NotImplementedError("We haven't yet supported fence region.")
|
|
|
|
|
else:
|
|
|
|
|
filler_pos = torch.rand(
|
|
|
|
|
(self.num_fillers, 2),
|
|
|
|
|
dtype=mov_node_size.dtype,
|
|
|
|
|
device=mov_node_size.device,
|
|
|
|
|
)
|
|
|
|
|
scale = self.die_ur - self.die_ll
|
|
|
|
|
shift = self.die_ll
|
|
|
|
|
filler_pos = filler_pos * scale + shift
|
|
|
|
|
mov_node_pos = torch.cat([mov_node_pos, filler_pos], dim=0)
|
|
|
|
|
mov_node_size = torch.cat([mov_node_size, self.filler_size], dim=0)
|
|
|
|
|
|
|
|
|
|
if args.noise_ratio > 0:
|
|
|
|
|
noise = torch.rand_like(mov_node_pos)
|
|
|
|
|
noise.sub_(0.5).mul_(mov_node_size).mul_(args.noise_ratio)
|
|
|
|
|
mov_node_pos += noise
|
|
|
|
|
|
|
|
|
|
expand_ratio = mov_node_pos.new_ones((mov_node_pos.shape[0]))
|
|
|
|
|
if self.clamp_node:
|
2023-04-06 13:34:26 +08:00
|
|
|
self.mov_node_size_real = mov_node_size.clone() # before expanding
|
2022-10-20 22:46:17 +08:00
|
|
|
mov_node_area = torch.prod(mov_node_size, 1)
|
|
|
|
|
clamp_mov_node_size = mov_node_size.clamp(min=self.unit_len * math.sqrt(2))
|
|
|
|
|
clamp_mov_node_area = torch.prod(clamp_mov_node_size, 1)
|
|
|
|
|
# update
|
|
|
|
|
expand_ratio = mov_node_area / clamp_mov_node_area
|
|
|
|
|
mov_node_size = clamp_mov_node_size
|
|
|
|
|
|
|
|
|
|
return mov_node_pos, mov_node_size, expand_ratio
|
|
|
|
|
|
|
|
|
|
def write_pl(self, node_pos, gp_prefix):
|
|
|
|
|
# support floating point based .pl file in global placement output
|
|
|
|
|
pl_file = gp_prefix + ".pl"
|
|
|
|
|
content = "UCLA pl 1.0\n"
|
|
|
|
|
# use float here
|
|
|
|
|
exact_node_pos = node_pos * self.die_scale + self.die_shift
|
|
|
|
|
exact_node_size = torch.round(self.node_size * self.die_scale)
|
|
|
|
|
tmp = (exact_node_size.div(self.site_width, rounding_mode="floor") == 1).bool()
|
|
|
|
|
is_terminal_ni = torch.logical_and(tmp[:, 0], tmp[:, 1]).cpu()
|
|
|
|
|
exact_node_lpos = (
|
|
|
|
|
(exact_node_pos - exact_node_size / 2).div_(self.site_width).cpu()
|
|
|
|
|
)
|
|
|
|
|
for i in range(self.num_nodes):
|
|
|
|
|
content += "\n%s %g %g : %s" % (
|
|
|
|
|
self.node_id2node_name[i],
|
|
|
|
|
exact_node_lpos[i, 0],
|
|
|
|
|
exact_node_lpos[i, 1],
|
|
|
|
|
"N"
|
|
|
|
|
)
|
|
|
|
|
if i >= self.fixed_index[0]:
|
|
|
|
|
if is_terminal_ni[i]:
|
|
|
|
|
content += " /FIXED_NI"
|
|
|
|
|
else:
|
|
|
|
|
content += " /FIXED"
|
|
|
|
|
with open(pl_file, "w") as f:
|
|
|
|
|
f.write(content)
|