blob: 7e46e130068dd3bd36e482238d33c00ce8b8b21e [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2020 Efabless Corporation
#
# 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.
"""
Reads in a structural verilog containing pads and a LEF file that
contains at least those pads and produces a DEF file with the padframe.
TODO:
core placement
config init,
external config
"""
import os
import sys
import argparse
from subprocess import Popen, PIPE
import odb
# TECH = ""
# PDK_ROOT = os.environ["PDK_ROOT"]
# os.environ["MAGTYPE"] = "maglef"
parser = argparse.ArgumentParser(description="Generates a padframe DEF")
# required if -cfg is not specified
parser.add_argument(
"--verilog-netlist",
"-verilog",
help="A structural verilog containing the pads (and other user macros)",
)
parser.add_argument(
"--def-netlist",
"-def",
help="A DEF file containing the unplaced pads (and other user macros)",
)
parser.add_argument("--design", "-d", help="Name of the top-level module")
parser.add_argument("--width", help="Width of the die area")
parser.add_argument("--height", help="Height of the die area")
parser.add_argument(
"--output-def", "-o", required=True, help="Name of the output file name"
)
parser.add_argument("--padframe-config", "-cfg", help="CFG file -- input to padring")
parser.add_argument(
"--pad-name-prefixes",
"-prefixes",
default=["sky130_fd_io", "sky130_ef_io"],
help="e.g., sky130_fd_io",
)
parser.add_argument(
"--init-padframe-config",
"-init",
action="store_true",
default=False,
help="Only generate a CFG file to be user edited",
)
parser.add_argument(
"--working-dir",
"-dir",
default=".",
help="Working directory to create temporary files needed",
)
parser.add_argument(
"--special-nets",
"-special",
nargs="+",
type=str,
default=None,
help="Net names to mark as special",
)
parser.add_argument(
"--lefs", "-l", nargs="+", type=str, default=None, required=True, help="LEF input"
)
args = parser.parse_args()
verilog_netlist = args.verilog_netlist
def_netlist = args.def_netlist
design = args.design
width = args.width
height = args.height
config_file_name = args.padframe_config
output_file_name = args.output_def
init_padframe_config_flag = args.init_padframe_config
working_dir = args.working_dir
lefs = args.lefs
special_nets = args.special_nets
pad_name_prefixes = args.pad_name_prefixes
working_def = f"{working_dir}/{design}.pf.def"
working_cfg = f"{working_dir}/{design}.pf.cfg"
for lef in lefs:
assert os.path.exists(lef), lef + " doesn't exist"
def invoke_padring(config_file_name, output_file_name):
print("Invoking padring to generate a padframe")
padring_command = []
padring_command.append("padring")
for lef in lefs:
padring_command.extend(["-L", lef])
padring_command.extend(["--def", output_file_name])
padring_command.append(config_file_name)
p = Popen(padring_command, stdout=PIPE, stdin=PIPE, stderr=PIPE, encoding="utf8")
output = p.communicate()
print("STDERR:")
print("\n".join(output[1].splitlines()[-10:]))
print("STDOUT:")
print(output[0].strip())
print("Padring exit code:", p.returncode)
assert p.returncode == 0, p.returncode
assert os.path.exists(output_file_name)
# hard requirement of a user netlist either as a DEF or verilog
# this is to ensure that the padframe will contain all pads in the design
# whether the config is autogenerated or user-provided
assert (
verilog_netlist is not None or def_netlist is not None
), "One of --verilog_netlist or --def-netlist is required"
# Step 1: create an openDB database from the verilog/def using OpenSTA's read_verilog
if verilog_netlist is not None:
assert (
def_netlist is None
), "Only one of --verilog_netlist or --def-netlist is required"
assert design is not None, "--design is required"
openroad_script = []
for lef in lefs:
openroad_script.append(f"read_lef {lef}")
openroad_script.append(f"read_verilog {verilog_netlist}")
openroad_script.append(f"link_design {design}")
openroad_script.append(f"write_def {working_def}")
# openroad_script.append(f"write_db {design}.pf.db")
openroad_script.append("exit")
p = Popen(["openroad"], stdout=PIPE, stdin=PIPE, stderr=PIPE, encoding="utf8")
openroad_script = "\n".join(openroad_script)
# print(openroad_script)
output = p.communicate(openroad_script)
print("STDOUT:")
print(output[0].strip())
print("STDERR:")
print(output[1].strip())
print("openroad exit code:", p.returncode)
assert p.returncode == 0, p.returncode
# TODO: check for errors
else:
assert def_netlist is not None
working_def = def_netlist
assert os.path.exists(working_def), "DEF file doesn't exist"
db_top = odb.dbDatabase.create()
# odb.read_db(db_top, f"{design}.pf.db")
for lef in lefs:
odb.read_lef(db_top, lef)
odb.read_def(db_top, working_def)
chip_top = db_top.getChip()
block_top = chip_top.getBlock()
top_design_name = block_top.getName()
print("Top-level design name:", top_design_name)
## Step 2: create a simple data structure with pads from the library
# types: corner, power, other
pads = {}
libs = db_top.getLibs()
for lib in libs:
masters = lib.getMasters()
for m in masters:
name = m.getName()
if m.isPad():
assert any(name.startswith(p) for p in pad_name_prefixes), name
print("Found pad:", name)
pad_type = m.getType()
pads[name] = pad_type
if pad_type == "PAD_SPACER":
print("Found PAD_SPACER:", name)
elif pad_type == "PAD_AREAIO":
# using this for special bus fillers...
print("Found PAD_AREAIO", name)
if m.isEndCap():
# FIXME: regular endcaps
assert any(name.startswith(p) for p in pad_name_prefixes), name
assert not m.isPad(), name + " is both pad and endcap?"
print("Found corner pad:", name)
pads[name] = "corner"
print()
print("The I/O library contains", len(pads), "cells")
print()
assert len(pads) != 0, "^"
## Step 3: Go over instances in the design and extract the used pads
def clean_name(name):
return name.replace("\\", "")
used_pads = []
used_corner_pads = []
other_instances = []
for inst in block_top.getInsts():
inst_name = inst.getName()
master_name = inst.getMaster().getName()
if inst.isPad():
assert any(master_name.startswith(p) for p in pad_name_prefixes), master_name
print("Found pad instance", inst_name, "of type", master_name)
used_pads.append((inst_name, master_name))
elif inst.isEndCap():
# FIXME: regular endcaps
assert any(master_name.startswith(p) for p in pad_name_prefixes), master_name
print("Found pad instance", inst_name, "of type", master_name)
print("Found corner pad instance", inst_name, "of type", master_name)
used_corner_pads.append((inst_name, master_name))
else:
assert not any(
master_name.startswith(p) for p in pad_name_prefixes
), master_name
other_instances.append(inst_name)
# FIXME: if used_corner_pads aren't supposed to be instantiated
assert len(used_corner_pads) == 4, used_corner_pads
print()
print(
"The user design contains",
len(used_pads),
"pads, 4 corner pads, and",
len(other_instances),
"other instances",
)
print()
assert len(used_pads) != 0, "^"
## Step 4: Generate a CFG or verify a user-provided config
def chunker(seq, size):
chunks = [seq[i::size] for i in range(size)]
# sort by type
chunks.sort(key=lambda pad_pair: pad_pair[1])
return chunks
def diff_lists(l1, l2):
return list(list(set(l1) - set(l2)) + list(set(l2) - set(l1)))
def generate_cfg(north, east, south, west, corner_pads, width, height):
cfg = []
cfg.append(f"AREA {width} {height} ;")
cfg.append("")
assert len(corner_pads) == 4, corner_pads
cfg.append(f"CORNER {corner_pads[0][0]} SW {corner_pads[0][1]} ;")
cfg.append(f"CORNER {corner_pads[1][0]} NW {corner_pads[1][1]} ;")
cfg.append(f"CORNER {corner_pads[2][0]} NE {corner_pads[2][1]} ;")
cfg.append(f"CORNER {corner_pads[3][0]} SE {corner_pads[3][1]} ;")
cfg.append("")
for pad in north:
cfg.append(f"PAD {pad[0]} N {pad[1]} ;")
cfg.append("")
for pad in east:
cfg.append(f"PAD {pad[0]} E {pad[1]} ;")
cfg.append("")
for pad in south:
cfg.append(f"PAD {pad[0]} S {pad[1]} ;")
cfg.append("")
for pad in west:
cfg.append(f"PAD {pad[0]} W {pad[1]} ;")
return "\n".join(cfg)
if config_file_name is not None:
assert os.path.exists(config_file_name), config_file_name + " doesn't exist"
with open(config_file_name, "r") as f:
lines = f.readlines()
user_config_pads = []
for line in lines:
if line.startswith("CORNER") or line.startswith("PAD"):
tokens = line.split()
assert len(tokens) == 5, tokens
inst_name, master_name = tokens[1], tokens[3]
if (
not pads[master_name] == "PAD_SPACER"
and not pads[master_name] == "PAD_AREAIO"
):
user_config_pads.append((inst_name, master_name))
elif line.startswith("AREA"):
tokens = line.split()
assert len(tokens) == 4, tokens
width = int(tokens[1])
height = int(tokens[2])
assert sorted(user_config_pads) == sorted(used_pads + used_corner_pads), (
"Mismatch between the provided config and the provided netlist. Diff:",
diff_lists(user_config_pads, used_pads + used_corner_pads),
)
print("User config verified")
working_cfg = config_file_name
else:
# TODO: get minimum width/height so that --width and --height aren't required
assert width is not None, "--width is required"
assert height is not None, "--height is required"
# auto generate a configuration
# TODO: after calssification, center power pads on each side
north, east, south, west = chunker(used_pads, 4)
with open(working_cfg, "w") as f:
f.write(generate_cfg(north, east, south, west, used_corner_pads, width, height))
if not init_padframe_config_flag:
invoke_padring(working_cfg, working_def)
else:
print(
"Padframe config generated at",
working_cfg,
f"Modify it and re-run this program with the '-cfg {working_cfg}' option",
)
sys.exit()
print("Applying pad placements to the design DEF")
db_padframe = odb.dbDatabase.create()
for lef in lefs:
odb.read_lef(db_padframe, lef)
odb.read_def(db_padframe, working_def)
chip_padframe = db_padframe.getChip()
block_padframe = chip_padframe.getBlock()
padframe_design_name = block_padframe.getName()
assert padframe_design_name == "PADRING", padframe_design_name
print("Padframe design name:", padframe_design_name)
# Mark special nets
if special_nets is not None:
for net in block_top.getNets():
net_name = net.getName()
if net_name in special_nets:
print("Marking", net_name, "as a special net")
net.setSpecial()
for iterm in net.getITerms():
iterm.setSpecial()
# get minimum width/height (core-bounded)
placed_cells_count = 0
created_cells_count = 0
for inst in block_padframe.getInsts():
assert inst.isPad() or inst.isEndCap(), (
inst.getName() + " is neither a pad nor corner pad"
)
inst_name = inst.getName()
master = inst.getMaster()
master_name = master.getName()
x, y = inst.getLocation()
orient = inst.getOrient()
if (inst_name, master_name) in used_pads + used_corner_pads:
original_inst = block_top.findInst(inst_name)
assert original_inst is not None, "Failed to find " + inst_name
assert original_inst.getPlacementStatus() == "NONE", (
inst_name + " is already placed"
)
original_inst.setOrient(orient)
original_inst.setLocation(x, y)
original_inst.setPlacementStatus("FIRM")
placed_cells_count += 1
else:
# must be a filler cell
new_inst = odb.dbInst_create(
block_top, db_top.findMaster(master_name), inst_name
)
assert new_inst is not None, "Failed to create " + inst_name
new_inst.setOrient(orient)
new_inst.setLocation(x, y)
new_inst.setPlacementStatus("FIRM")
created_cells_count += 1
# TODO: place the core macros within the padframe (chip floorplan)
for inst in block_top.getInsts():
if inst.isPlaced() or inst.isFixed():
continue
print("Placing", inst.getName())
master = inst.getMaster()
master_width = master.getWidth()
master_height = master.getHeight()
print(master_width, master_height)
print(width, height)
inst.setLocation(
width * 1000 // 2 - master_width // 2, height * 1000 // 2 - master_height // 2
)
inst.setPlacementStatus("PLACED")
odb.write_def(block_top, output_file_name)
print("Done")