Files
OpenLane/scripts/padringer.py
Donn 543144f68d Migrate to a more current version of OpenROAD (#433)
* Initial Set Of Changes
+ Made openroad binary customizable with OPENROAD_BIN env var: defaults to `openroad`
+ OL Install allows for customizable flow.tcl for testing
+ OR Issue now explicitly requires input and output defs as arguments
+ Updated routing commands
- Removed standalone tritonroute
- Removed CTS sqr_cap/sqr_rest options (no longer supported)

* Partial Merge Of The-OpenROAD-Project/OpenLane#472

Co-authored-by: Osama Hammad <osama21@aucegypt.edu>

* Fixed Docker Environment
`import opendbpy` -> `opendb`
eigen is an archive now
Removed Diamond Search Height Completely

* Remove Minimum Distance, FP w/ layer numbers

(per discussion with @osamahammad21)

* Update PDK

~ Install new version of Git to handle Open_PDK's cloning woes
~ Update Commit Hash With Fixes To `download.sh`
~ Address The-OpenROAD-Project/OpenLane#475 while I'm here

* Update OpenROAD, Remove Standalone OpenSTA

* Remove Standalone OpenDP

* Update TritonRoute Invocation

+ Random Seed Specified
+ Removed deprecated values from .params file
(Not gonna remove .params file just yet)
+ Updated or_issue.py to handle incomplete file paths

* update report layer usage

* Makefile Tweaks

- Decreased duplication
~ Now using long flags (See #476)

* Updates to run_designs, OR commit

* number of grt antenna repairer iterations

* Update Magic & Netgen for LVS Issue

- Remove blabla from completeTestSet pending RTimothyEdwards/netgen#21
+ Update Magic and Netgen to same versions as master
+  Update Readme to replace efabless links with OpenLane ones

* Update Magic/Netgen to Latest Versions

- Remove usb_cdc_core from fastestTestSet pending RTimothyEdwards/netgen#21

Co-authored-by: Osama Hammad <osama21@aucegypt.edu>
2021-07-07 14:55:19 +02:00

416 lines
14 KiB
Python

#!/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 random
import argparse
from subprocess import Popen, PIPE, STDOUT
import opendb as 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(f"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):
l = [seq[i::size] for i in range(size)]
# sort by type
l.sort(key=lambda pad_pair: pad_pair[1])
return l
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")