Merge branch 'dev' into cacti_model

This commit is contained in:
Hunter Nichols 2021-09-20 17:01:50 -07:00
commit 39ae1270d7
28 changed files with 2073 additions and 236 deletions

View File

@ -8,7 +8,7 @@ jobs:
uses: actions/checkout@v1
- name: SCMOS test
run: |
. /home/github-runner/setup-paths.sh
. /home/github-runner/setup-paths.sh
export OPENRAM_HOME="${{ github.workspace }}/compiler"
export OPENRAM_TECH="${{ github.workspace }}/technology:/software/PDKs/skywater-tech"
export OPENRAM_TMP="${{ github.workspace }}/scn4me_subm_temp"
@ -27,7 +27,7 @@ jobs:
uses: actions/checkout@v1
- name: FreePDK45 test
run: |
. /home/github-runner/setup-paths.sh
. /home/github-runner/setup-paths.sh
export OPENRAM_HOME="${{ github.workspace }}/compiler"
export OPENRAM_TECH="${{ github.workspace }}/technology:/software/PDKs/skywater-tech"
export OPENRAM_TMP="${{ github.workspace }}/freepdk45_temp"
@ -54,4 +54,3 @@ jobs:
# with:
# name: code-coverage-report
# path: ${{ github.workspace }}/coverage_html/

View File

@ -5,10 +5,11 @@
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import debug
import math
import tech
class vector():
"""
This is the vector class to represent the coordinate

View File

@ -153,7 +153,7 @@ class verilog:
self.vf.write(" wmask{0}_reg = wmask{0};\n".format(port))
if self.num_spare_cols:
self.vf.write(" spare_wen{0}_reg = spare_wen{0};\n".format(port))
self.vf.write(" addr{0}_reg = addr{0};\n".format(port))
self.vf.write(" addr{0}_reg = addr{0};\n".format(port))
if port in self.read_ports:
self.add_write_read_checks(port)
@ -190,15 +190,15 @@ class verilog:
self.vf.write(" input csb{0}; // active low chip select\n".format(port))
if port in self.readwrite_ports:
self.vf.write(" input web{0}; // active low write control\n".format(port))
self.vf.write(" input [ADDR_WIDTH-1:0] addr{0};\n".format(port))
if port in self.write_ports:
if self.write_size:
self.vf.write(" input [NUM_WMASKS-1:0] wmask{0}; // write mask\n".format(port))
if self.num_spare_cols == 1:
self.vf.write(" input spare_wen{0}; // spare mask\n".format(port))
elif self.num_spare_cols > 1:
self.vf.write(" input [{1}:0] spare_wen{0}; // spare mask\n".format(port, self.num_spare_cols-1))
self.vf.write(" input [ADDR_WIDTH-1:0] addr{0};\n".format(port))
if port in self.write_ports:
self.vf.write(" input [DATA_WIDTH-1:0] din{0};\n".format(port))
if port in self.read_ports:
self.vf.write(" output [DATA_WIDTH-1:0] dout{0};\n".format(port))

View File

@ -31,7 +31,7 @@ class lib:
self.gnd_name = spice["ground"]
except KeyError:
self.gnd_name = "gnd"
self.out_dir = out_dir
self.sram = sram
self.sp_file = sp_file
@ -60,7 +60,7 @@ class lib:
self.load_scales = np.array(OPTS.load_scales)
self.load = tech.spice["dff_in_cap"]
self.loads = self.load_scales * self.load
self.slew_scales = np.array(OPTS.slew_scales)
self.slew = tech.spice["rise_time"]
@ -79,7 +79,7 @@ class lib:
self.slews.append(slew)
self.loads = np.array(self.loads)
self.slews = np.array(self.slews)
debug.info(1, "Slews: {0}".format(self.slews))
debug.info(1, "Slews: {0}".format(self.slews))
debug.info(1, "Loads: {0}".format(self.loads))
debug.info(1, "self.load_slews : {0}".format(self.load_slews))
def create_corners(self):
@ -102,7 +102,7 @@ class lib:
self.corners = []
self.lib_files = []
if OPTS.use_specified_corners == None:
# Nominal corner
corner_tuples = set()
@ -114,7 +114,7 @@ class lib:
for v in self.supply_voltages:
for t in self.temperatures:
corner_tuples.add((p, v, t))
else:
else:
nom_corner = (nom_process, nom_supply, nom_temperature)
corner_tuples.add(nom_corner)
if not OPTS.nominal_corner_only:
@ -132,7 +132,7 @@ class lib:
corner_tuples.remove(nom_corner)
else:
corner_tuples = OPTS.use_specified_corners
for corner_tuple in corner_tuples:
self.add_corner(*corner_tuple)
@ -366,16 +366,16 @@ class lib:
self.lib.write(" base_type : array;\n")
self.lib.write(" data_type : bit;\n")
self.lib.write(" bit_width : {0};\n".format(self.sram.word_size))
self.lib.write(" bit_from : 0;\n")
self.lib.write(" bit_to : {0};\n".format(self.sram.word_size - 1))
self.lib.write(" bit_from : {0};\n".format(self.sram.word_size - 1))
self.lib.write(" bit_to : 0;\n")
self.lib.write(" }\n\n")
self.lib.write(" type (addr){\n")
self.lib.write(" base_type : array;\n")
self.lib.write(" data_type : bit;\n")
self.lib.write(" bit_width : {0};\n".format(self.sram.addr_size))
self.lib.write(" bit_from : 0;\n")
self.lib.write(" bit_to : {0};\n".format(self.sram.addr_size - 1))
self.lib.write(" bit_from : {0};\n".format(self.sram.addr_size - 1))
self.lib.write(" bit_to : 0;\n")
self.lib.write(" }\n\n")
if self.sram.write_size:
@ -383,8 +383,8 @@ class lib:
self.lib.write(" base_type : array;\n")
self.lib.write(" data_type : bit;\n")
self.lib.write(" bit_width : {0};\n".format(self.sram.num_wmasks))
self.lib.write(" bit_from : 0;\n")
self.lib.write(" bit_to : {0};\n".format(self.sram.num_wmasks - 1))
self.lib.write(" bit_from : {0};\n".format(self.sram.num_wmasks - 1))
self.lib.write(" bit_to : 0;\n")
self.lib.write(" }\n\n")
@ -652,21 +652,21 @@ class lib:
probe_address = "0" + "1" * (self.sram.addr_size - 1)
probe_data = self.sram.word_size - 1
char_results = self.d.analyze(probe_address, probe_data, self.load_slews)
self.char_sram_results, self.char_port_results = char_results
if 'sim_time' in self.char_sram_results:
self.pred_time = self.char_sram_results['sim_time']
# Add to the OPTS to be written out as part of the extended OPTS file
# FIXME: Temporarily removed from characterization output
# FIXME: Temporarily removed from characterization output
# if not self.use_model:
# OPTS.sen_path_delays = self.char_sram_results["sen_path_measures"]
# OPTS.sen_path_names = self.char_sram_results["sen_path_names"]
# OPTS.bl_path_delays = self.char_sram_results["bl_path_measures"]
# OPTS.bl_path_names = self.char_sram_results["bl_path_names"]
def compute_setup_hold(self):
""" Do the analysis if we haven't characterized a FF yet """
# Do the analysis if we haven't characterized a FF yet
@ -689,23 +689,23 @@ class lib:
datasheet = open(datasheet_path +'/datasheet.info', 'w')
else:
datasheet = open(datasheet_path +'/datasheet.info', 'a+')
self.write_inp_params_datasheet(datasheet, corner, lib_name)
self.write_signal_from_ports(datasheet,
"din{1}[{0}:0]".format(self.sram.word_size - 1, '{}'),
self.write_ports,
"din{1}[{0}:0]".format(self.sram.word_size - 1, '{}'),
self.write_ports,
"setup_times_LH",
"setup_times_HL",
"setup_times_HL",
"hold_times_LH",
"hold_times_HL")
# self.write_signal_from_ports(datasheet,
# "dout{1}[{0}:0]".format(self.sram.word_size - 1, '{}'),
# self.read_ports,
# "dout{1}[{0}:0]".format(self.sram.word_size - 1, '{}'),
# self.read_ports,
# "delay_lh",
# "delay_hl",
# "delay_hl",
# "slew_lh",
# "slew_hl")
# "slew_hl")
for port in self.all_ports:
#dout timing
if port in self.read_ports:
@ -722,41 +722,41 @@ class lib:
min(list(map(round_time,self.char_port_results[port]["slew_hl"]))),
max(list(map(round_time,self.char_port_results[port]["slew_hl"])))
))
))
self.write_signal_from_ports(datasheet,
"csb{}",
self.all_ports,
"csb{}",
self.all_ports,
"setup_times_LH",
"setup_times_HL",
"setup_times_HL",
"hold_times_LH",
"hold_times_HL")
self.write_signal_from_ports(datasheet,
"addr{1}[{0}:0]".format(self.sram.addr_size - 1, '{}'),
self.all_ports,
"setup_times_LH",
"setup_times_HL",
"hold_times_LH",
"hold_times_HL")
self.write_signal_from_ports(datasheet,
"web{}",
self.readwrite_ports,
"addr{1}[{0}:0]".format(self.sram.addr_size - 1, '{}'),
self.all_ports,
"setup_times_LH",
"setup_times_HL",
"setup_times_HL",
"hold_times_LH",
"hold_times_HL")
"hold_times_HL")
self.write_signal_from_ports(datasheet,
"web{}",
self.readwrite_ports,
"setup_times_LH",
"setup_times_HL",
"hold_times_LH",
"hold_times_HL")
self.write_power_datasheet(datasheet)
self.write_model_params(datasheet, time)
datasheet.write("END\n")
datasheet.close()
def write_inp_params_datasheet(self, datasheet, corner, lib_name):
if OPTS.is_unit_test:
git_id = 'FFFFFFFFFFFFFFFFFFFF'
@ -776,7 +776,7 @@ class lib:
debug.warning("Failed to retrieve git id")
git_id = 'Failed to retrieve'
current_time = datetime.date.today()
# write static information to be parser later
datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},".format(
OPTS.output_name,
@ -804,8 +804,8 @@ class lib:
# write area
datasheet.write(str(self.sram.width * self.sram.height) + ',')
def write_signal_from_ports(self, datasheet, signal, ports, time_pos_1, time_pos_2, time_pos_3, time_pos_4):
def write_signal_from_ports(self, datasheet, signal, ports, time_pos_1, time_pos_2, time_pos_3, time_pos_4):
for port in ports:
datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},".format(
signal.format(port),
@ -822,8 +822,8 @@ class lib:
max(list(map(round_time,self.times[time_pos_4])))
))
def write_power_datasheet(self, datasheet):
def write_power_datasheet(self, datasheet):
# write power information
for port in self.all_ports:
name = ''
@ -862,34 +862,30 @@ class lib:
control_str += ' & csb{0}'.format(i)
datasheet.write("{0},{1},{2},".format('leak', control_str, self.char_sram_results["leakage_power"]))
def write_model_params(self, datasheet, time):
"""Write values which will be used in the analytical model as inputs"""
datasheet.write("{0},{1},".format('sim_time', time))
datasheet.write("{0},{1},".format('words_per_row', OPTS.words_per_row))
datasheet.write("{0},{1},".format('slews', list(self.slews)))
datasheet.write("{0},{1},".format('loads', list(self.loads)))
for port in self.read_ports:
datasheet.write("{0},{1},".format('cell_rise_{}'.format(port), self.char_port_results[port]["delay_lh"]))
datasheet.write("{0},{1},".format('cell_fall_{}'.format(port), self.char_port_results[port]["delay_hl"]))
datasheet.write("{0},{1},".format('rise_transition_{}'.format(port), self.char_port_results[port]["slew_lh"]))
datasheet.write("{0},{1},".format('fall_transition_{}'.format(port), self.char_port_results[port]["slew_hl"]))
for port in self.write_ports:
write1_power = np.mean(self.char_port_results[port]["write1_power"])
write0_power = np.mean(self.char_port_results[port]["write0_power"])
datasheet.write("{0},{1},".format('write_rise_power_{}'.format(port), write1_power))
#FIXME: should be write_fall_power
datasheet.write("{0},{1},".format('write_fall_power_{}'.format(port), write0_power))
for port in self.read_ports:
read1_power = np.mean(self.char_port_results[port]["read1_power"])
read0_power = np.mean(self.char_port_results[port]["read0_power"])
datasheet.write("{0},{1},".format('read_rise_power_{}'.format(port), read1_power))
#FIXME: should be read_fall_power
datasheet.write("{0},{1},".format('read_fall_power_{}'.format(port), read0_power))

View File

@ -22,7 +22,7 @@ import getpass
import subprocess
VERSION = "1.1.15"
VERSION = "1.1.18"
NAME = "OpenRAM v{}".format(VERSION)
USAGE = "openram.py [options] <config file>\nUse -h for help.\n"

View File

@ -369,11 +369,9 @@ class bank(design.design):
3 * self.m2_pitch,
drc("nwell_to_nwell"))
def add_modules(self):
""" Add all the modules using the class loader """
local_array_size = OPTS.local_array_size
if local_array_size > 0:
@ -705,7 +703,7 @@ class bank(design.design):
pitch=self.m3_pitch)
self.copy_layout_pin(self.port_address_inst[0], "wl_en", self.prefix + "wl_en0")
# Port 1
if len(self.all_ports)==2:
# The other control bus is routed up to two pitches above the bitcell array

View File

@ -121,8 +121,10 @@ class control_logic(design.design):
# max_fanout = max(self.num_rows, self.num_cols)
# wl_en drives every row in the bank
# MRG 9/3/2021: Ensure that this is two stages to prevent race conditions with the write driver
size_list = [max(int(self.num_rows / 9), 1), max(int(self.num_rows / 3), 1)]
self.wl_en_driver = factory.create(module_type="pdriver",
fanout=self.num_rows,
size_list=size_list,
height=dff_height)
self.add_mod(self.wl_en_driver)
@ -348,7 +350,7 @@ class control_logic(design.design):
row += 1
control_center_y = self.wl_en_inst.uy() + self.m3_pitch
# Delay chain always gets placed at row 4
self.place_delay(4)
height = self.delay_inst.uy()
@ -391,7 +393,7 @@ class control_logic(design.design):
def place_delay(self, row):
""" Place the replica bitline """
debug.check(row % 2 == 0, "Must place delay chain at even row for supply alignment.")
# It is flipped on X axis
y_off = row * self.and2.height + self.delay_chain.height

View File

@ -302,4 +302,3 @@ class local_bitcell_array(bitcell_base_array.bitcell_base_array):
Clears the bit exclusions
"""
self.bitcell_array.clear_exclude_bits()

View File

@ -43,6 +43,21 @@ class sram_config:
self.compute_sizes()
def __str__(self):
""" override print function output """
config_items = ["num_banks",
"word_size",
"num_words",
"words_per_row",
"write_size",
"num_spare_rows",
"num_spare_cols"]
str = ""
for item in config_items:
val = getattr(self, item)
str += "{} : {}\n".format(item, val)
return str
def set_local_config(self, module):
""" Copy all of the member variables to the given module for convenience """

View File

@ -49,7 +49,7 @@ class model_delay_test(openram_test):
debug.info(1, "Probe address {0} probe data bit {1}".format(probe_address, probe_data))
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
d = delay(s.s, tempspice, corner)
m = elmore(s.s, tempspice, corner)
import tech
@ -77,9 +77,9 @@ class model_delay_test(openram_test):
debug.info(1,"Model Delays={}".format(model_delays))
if OPTS.tech_name == "freepdk45":
error_tolerance = 0.25
error_tolerance = 0.30
elif OPTS.tech_name == "scn4m_subm":
error_tolerance = 0.25
error_tolerance = 0.30
else:
self.assertTrue(False) # other techs fail

View File

@ -60,20 +60,20 @@ class timing_sram_test(openram_test):
data.update(port_data[0])
if OPTS.tech_name == "freepdk45":
golden_data = {'delay_hl': [0.24042560000000002],
'delay_lh': [0.24042560000000002],
'disabled_read0_power': [0.8981647999999998],
'disabled_read1_power': [0.9101543999999998],
'disabled_write0_power': [0.9270382999999998],
'disabled_write1_power': [0.9482969999999998],
'leakage_power': 2.9792199999999998,
golden_data = {'delay_hl': [0.2314011],
'delay_lh': [0.2314011],
'disabled_read0_power': [0.173459901],
'disabled_read1_power': [0.185612201],
'disabled_write0_power': [0.202493001],
'disabled_write1_power': [0.224080601],
'leakage_power': 0.0017065770000000001,
'min_period': 0.938,
'read0_power': [1.1107930999999998],
'read1_power': [1.1143252999999997],
'slew_hl': [0.2800772],
'slew_lh': [0.2800772],
'write0_power': [1.1667769],
'write1_power': [1.0986076999999999]}
'read0_power': [0.372276201],
'read1_power': [0.37621480100000004],
'slew_hl': [0.27947489999999997],
'slew_lh': [0.27947489999999997],
'write0_power': [0.429895901],
'write1_power': [0.383337501]}
elif OPTS.tech_name == "scn4m_subm":
golden_data = {'delay_hl': [1.884186],
'delay_lh': [1.884186],

View File

@ -36,6 +36,7 @@ class riscv_func_test(openram_test):
OPTS.num_rw_ports = 1
OPTS.num_w_ports = 0
OPTS.num_r_ports = 0
OPTS.local_array_size = 16
globals.setup_bitcell()
# This is a hack to reload the characterizer __init__ with the spice version

View File

@ -16,7 +16,7 @@ from sram_factory import factory
import debug
# @unittest.skip("SKIPPING 50_riscv_phys_test")
#@unittest.skip("SKIPPING 50_riscv_phys_test")
class riscv_phys_test(openram_test):
def runTest(self):
@ -24,8 +24,15 @@ class riscv_phys_test(openram_test):
globals.init_openram(config_file)
from sram_config import sram_config
if OPTS.tech_name == "sky130":
num_spare_rows = 1
num_spare_cols = 1
else:
num_spare_rows = 0
num_spare_cols = 0
OPTS.num_rw_ports = 1
OPTS.num_r_ports = 1
OPTS.num_r_ports = 0
OPTS.num_w_ports = 0
OPTS.local_array_size = 16
globals.setup_bitcell()
@ -36,9 +43,9 @@ class riscv_phys_test(openram_test):
write_size=8,
num_words=32,
num_banks=1,
num_spare_rows=1,
num_spare_cols=1)
c.words_per_row=2
num_spare_cols=num_spare_cols,
num_spare_rows=num_spare_rows)
c.words_per_row=1
c.recompute_sizes()
debug.info(1, "Layout test for {}rw,{}r,{}w sram "
"with {} bit words, {} words, {} words per "

View File

@ -6,7 +6,7 @@
# All rights reserved.
#
from globals import OPTS
word_size = 1
word_size = 2
num_words = 16
tech_name = OPTS.tech_name

View File

@ -6,7 +6,7 @@
# All rights reserved.
#
from globals import OPTS
word_size = 1
word_size = 2
num_words = 16
tech_name = OPTS.tech_name

View File

@ -32,14 +32,16 @@ if not OPTS.check_lvsdrc:
# OPTS.magic_exe = None
else:
debug.info(1, "Finding DRC/LVS/PEX tools.")
OPTS.drc_exe = get_tool("DRC", ["calibre", "assura", "magic"], drc_name)
OPTS.lvs_exe = get_tool("LVS", ["calibre", "assura", "netgen"], lvs_name)
OPTS.pex_exe = get_tool("PEX", ["calibre", "magic"], pex_name)
OPTS.drc_exe = get_tool("DRC", ["klayout", "magic", "calibre", "assura"], drc_name)
OPTS.lvs_exe = get_tool("LVS", ["klayout", "netgen", "calibre", "assura"], lvs_name)
OPTS.pex_exe = get_tool("PEX", ["klayout", "magic", "calibre"], pex_name)
# if OPTS.tech_name == "sky130":
# OPTS.magic_exe = get_tool("GDS", ["magic"])
if not OPTS.drc_exe:
from .none import run_drc, print_drc_stats, write_drc_script
elif "klayout"==OPTS.drc_exe[0]:
from .klayout import run_drc, print_drc_stats, write_drc_script
elif "calibre"==OPTS.drc_exe[0]:
from .calibre import run_drc, print_drc_stats, write_drc_script
elif "assura"==OPTS.drc_exe[0]:
@ -52,6 +54,8 @@ else:
if not OPTS.lvs_exe:
from .none import run_lvs, print_lvs_stats, write_lvs_script
elif "klayout"==OPTS.lvs_exe[0]:
from .klayout import run_lvs, print_lvs_stats, write_lvs_script
elif "calibre"==OPTS.lvs_exe[0]:
from .calibre import run_lvs, print_lvs_stats, write_lvs_script
elif "assura"==OPTS.lvs_exe[0]:
@ -65,6 +69,8 @@ else:
if not OPTS.pex_exe:
from .none import run_pex, print_pex_stats
elif "klayout"==OPTS.pex_exe[0]:
from .klayout import run_pex, print_pex_stats
elif "calibre"==OPTS.pex_exe[0]:
from .calibre import run_pex, print_pex_stats
elif "magic"==OPTS.pex_exe[0]:
@ -78,4 +84,3 @@ else:
# from .magic import filter_gds
# else:
# debug.warning("Did not find Magic.")

266
compiler/verify/klayout.py Normal file
View File

@ -0,0 +1,266 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2021 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
"""
This is a DRC/LVS/PEX interface file for klayout.
"""
import os
import re
import shutil
import debug
from globals import OPTS
from run_script import *
# Keep track of statistics
num_drc_runs = 0
num_lvs_runs = 0
num_pex_runs = 0
def write_drc_script(cell_name, gds_name, extract, final_verification, output_path, sp_name=None):
"""
Write a klayout script to perform DRC and optionally extraction.
"""
global OPTS
# DRC:
# klayout -b -r drc_FreePDK45.lydrc -rd input=sram_8_256_freepdk45.gds -rd topcell=sram_8_256_freepdk45 -rd output=drc_FreePDK45.lyrdb
# Copy .lydrc file into the output directory
full_drc_file = OPTS.openram_tech + "tech/{}.lydrc".format(OPTS.tech_name)
drc_file = os.path.basename(full_drc_file)
if os.path.exists(full_drc_file):
shutil.copy(full_drc_file, output_path)
else:
debug.warning("Could not locate file: {}".format(full_drc_file))
# Create an auxiliary script to run calibre with the runset
run_file = output_path + "run_drc.sh"
f = open(run_file, "w")
f.write("#!/bin/sh\n")
cmd = "{0} -b -r {1} -rd input={2} -rd topcell={3} -rd output={3}.drc.report".format(OPTS.drc_exe[1],
drc_file,
gds_name,
cell_name)
f.write(cmd)
f.write("\n")
f.close()
os.system("chmod u+x {}".format(run_file))
def run_drc(cell_name, gds_name, sp_name=None, extract=True, final_verification=False):
"""Run DRC check on a cell which is implemented in gds_name."""
global num_drc_runs
num_drc_runs += 1
write_drc_script(cell_name, gds_name, extract, final_verification, OPTS.openram_temp, sp_name=sp_name)
(outfile, errfile, resultsfile) = run_script(cell_name, "drc")
# Check the result for these lines in the summary:
# Total DRC errors found: 0
# The count is shown in this format:
# Cell replica_cell_6t has 3 error tiles.
# Cell tri_gate_array has 8 error tiles.
# etc.
try:
f = open(resultsfile, "r")
except FileNotFoundError:
debug.error("Unable to load DRC results file from {}. Is klayout set up?".format(resultsfile), 1)
results = f.readlines()
f.close()
errors=len([x for x in results if "<visited>" in x])
# always display this summary
result_str = "DRC Errors {0}\t{1}".format(cell_name, errors)
if errors > 0:
debug.warning(result_str)
else:
debug.info(1, result_str)
return errors
def write_lvs_script(cell_name, gds_name, sp_name, final_verification=False, output_path=None):
""" Write a klayout script to perform LVS. """
# LVS:
# klayout -b -rd input=sram_32_2048_freepdk45.gds -rd report=my_report.lyrdb -rd schematic=sram_32_2048_freepdk45.sp -rd target_netlist=sram_32_2048_freepdk45_extracted.cir -r lvs_freepdk45.lvs
global OPTS
if not output_path:
output_path = OPTS.openram_temp
# Copy .lylvs file into the output directory
full_lvs_file = OPTS.openram_tech + "tech/{}.lylvs".format(OPTS.tech_name)
lvs_file = os.path.basename(full_lvs_file)
if os.path.exists(full_lvs_file):
shutil.copy(full_lvs_file, output_path)
else:
debug.warning("Could not locate file: {}".format(full_lvs_file))
run_file = output_path + "/run_lvs.sh"
f = open(run_file, "w")
f.write("#!/bin/sh\n")
cmd = "{0} -b -r {1} -rd input={2} -rd report={4}.lvs.report -rd schematic={3} -rd target_netlist={4}.spice".format(OPTS.lvs_exe[1],
lvs_file,
gds_name,
sp_name,
cell_name)
f.write(cmd)
f.write("\n")
f.close()
os.system("chmod u+x {}".format(run_file))
def run_lvs(cell_name, gds_name, sp_name, final_verification=False, output_path=None):
"""Run LVS check on a given top-level name which is
implemented in gds_name and sp_name. Final verification will
ensure that there are no remaining virtual conections. """
global num_lvs_runs
num_lvs_runs += 1
if not output_path:
output_path = OPTS.openram_temp
write_lvs_script(cell_name, gds_name, sp_name, final_verification)
(outfile, errfile, resultsfile) = run_script(cell_name, "lvs")
# check the result for these lines in the summary:
try:
f = open(outfile, "r")
except FileNotFoundError:
debug.error("Unable to load LVS results from {}".format(outfile), 1)
results = f.readlines()
f.close()
# Look for CONGRATULATIONS or ERROR
congrats = len([x for x in results if "CONGRATULATIONS" in x])
total_errors = len([x for x in results if "ERROR" in x])
if total_errors>0:
debug.error("{0}\tLVS mismatch (results in {1})".format(cell_name, resultsfile))
elif congrats>0:
debug.info(1, "{0}\tLVS matches".format(cell_name))
else:
debug.info(1, "{0}\tNo LVS result".format(cell_name))
total_errors += 1
return total_errors
def run_pex(name, gds_name, sp_name, output=None, final_verification=False, output_path=None):
"""Run pex on a given top-level name which is
implemented in gds_name and sp_name. """
debug.error("PEX not implemented", -1)
global num_pex_runs
num_pex_runs += 1
if not output_path:
output_path = OPTS.openram_temp
os.chdir(output_path)
if not output_path:
output_path = OPTS.openram_temp
if output == None:
output = name + ".pex.netlist"
# check if lvs report has been done
# if not run drc and lvs
if not os.path.isfile(name + ".lvs.report"):
run_drc(name, gds_name)
run_lvs(name, gds_name, sp_name)
# # pex_fix did run the pex using a script while dev orignial method
# # use batch mode.
# # the dev old code using batch mode does not run and is split into functions
# pex_runset = write_script_pex_rule(gds_name, name, sp_name, output)
# errfile = "{0}{1}.pex.err".format(output_path, name)
# outfile = "{0}{1}.pex.out".format(output_path, name)
# script_cmd = "{0} 2> {1} 1> {2}".format(pex_runset,
# errfile,
# outfile)
# cmd = script_cmd
# debug.info(2, cmd)
# os.system(cmd)
# # rename technology models
# pex_nelist = open(output, 'r')
# s = pex_nelist.read()
# pex_nelist.close()
# s = s.replace('pfet', 'p')
# s = s.replace('nfet', 'n')
# f = open(output, 'w')
# f.write(s)
# f.close()
# # also check the output file
# f = open(outfile, "r")
# results = f.readlines()
# f.close()
# out_errors = find_error(results)
# debug.check(os.path.isfile(output), "Couldn't find PEX extracted output.")
# correct_port(name, output, sp_name)
return out_errors
def write_batch_pex_rule(gds_name, name, sp_name, output):
"""
"""
# write the runset file
file = OPTS.openram_temp + "pex_runset"
f = open(file, "w")
f.close()
return file
def write_script_pex_rule(gds_name, cell_name, sp_name, output):
global OPTS
run_file = OPTS.openram_temp + "run_pex.sh"
f = open(run_file, "w")
f.write("#!/bin/sh\n")
f.write('export OPENRAM_TECH="{}"\n'.format(os.environ['OPENRAM_TECH']))
f.write('echo "$(date): Starting PEX using Klayout {}"\n'.format(OPTS.drc_exe[1]))
f.write("retcode=$?\n")
f.write("mv {0}.spice {1}\n".format(cell_name, output))
f.write('echo "$(date): Finished PEX using Klayout {}"\n'.format(OPTS.drc_exe[1]))
f.write("exit $retcode\n")
f.close()
os.system("chmod u+x {}".format(run_file))
return run_file
def print_drc_stats():
debug.info(1, "DRC runs: {0}".format(num_drc_runs))
def print_lvs_stats():
debug.info(1, "LVS runs: {0}".format(num_lvs_runs))
def print_pex_stats():
debug.info(1, "PEX runs: {0}".format(num_pex_runs))

View File

@ -71,15 +71,15 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa
global OPTS
# Copy .magicrc file into the output directory
magic_file = os.environ.get('OPENRAM_MAGICRC', None)
if not magic_file:
magic_file = OPTS.openram_tech + "tech/.magicrc"
full_magic_file = os.environ.get('OPENRAM_MAGICRC', None)
if not full_magic_file:
full_magic_file = OPTS.openram_tech + "tech/.magicrc"
if os.path.exists(magic_file):
shutil.copy(magic_file, output_path + "/.magicrc")
if os.path.exists(full_magic_file):
shutil.copy(full_magic_file, output_path + "/.magicrc")
else:
debug.warning("Could not locate .magicrc file: {}".format(magic_file))
debug.warning("Could not locate .magicrc file: {}".format(full_magic_file))
run_file = output_path + "run_ext.sh"
f = open(run_file, "w")
f.write("#!/bin/sh\n")
@ -96,7 +96,7 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa
f.write("gds warning default\n")
# These two options are temporarily disabled until Tim fixes a bug in magic related
# to flattening channel routes and vias (hierarchy with no devices in it). Otherwise,
# they appear to be disconnected.
# they appear to be disconnected.
f.write("gds flatten true\n")
f.write("gds ordering true\n")
f.write("gds readonly true\n")
@ -106,7 +106,7 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa
f.write('puts "Finished loading cell {}"\n'.format(cell_name))
f.write("cellname delete \\(UNNAMED\\)\n")
f.write("writeall force\n")
# Extract
if not sp_name:
f.write("port makeall\n")
@ -142,7 +142,7 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa
f.write(pre + "ext2spice format ngspice\n")
f.write(pre + "ext2spice {}\n".format(cell_name))
f.write('puts "Finished ext2spice"\n')
f.write("quit -noprompt\n")
f.write("EOF\n")
f.write("magic_retcode=$?\n")
@ -165,7 +165,7 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa
mag_file = OPTS.openram_tech + "maglef_lib/" + blackbox_cell_name + ".mag"
debug.check(os.path.isfile(mag_file), "Could not find blackbox cell {}".format(mag_file))
f.write('cp {0} .\n'.format(mag_file))
f.write('echo "$(date): Starting DRC using Magic {}"\n'.format(OPTS.drc_exe[1]))
f.write('\n')
f.write("{} -dnull -noconsole << EOF\n".format(OPTS.drc_exe[1]))
@ -193,7 +193,7 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa
f.close()
os.system("chmod u+x {}".format(run_file))
def run_drc(cell_name, gds_name, sp_name=None, extract=True, final_verification=False):
"""Run DRC check on a cell which is implemented in gds_name."""
@ -202,9 +202,9 @@ def run_drc(cell_name, gds_name, sp_name=None, extract=True, final_verification=
num_drc_runs += 1
write_drc_script(cell_name, gds_name, extract, final_verification, OPTS.openram_temp, sp_name=sp_name)
(outfile, errfile, resultsfile) = run_script(cell_name, "ext")
(outfile, errfile, resultsfile) = run_script(cell_name, "drc")
# Check the result for these lines in the summary:
@ -252,13 +252,14 @@ def write_lvs_script(cell_name, gds_name, sp_name, final_verification=False, out
output_path = OPTS.openram_temp
# Copy setup.tcl file into the output directory
setup_file = os.environ.get('OPENRAM_NETGENRC', None)
if not setup_file:
setup_file = OPTS.openram_tech + "tech/setup.tcl"
if os.path.exists(setup_file):
full_setup_file = os.environ.get('OPENRAM_NETGENRC', None)
if not full_setup_file:
full_setup_file = OPTS.openram_tech + "tech/setup.tcl"
setup_file = os.path.basename(full_setup_file)
if os.path.exists(full_setup_file):
# Copy setup.tcl file into temp dir
shutil.copy(setup_file, output_path)
shutil.copy(full_setup_file, output_path)
else:
setup_file = 'nosetup'
@ -290,7 +291,7 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False, output_path=
if not output_path:
output_path = OPTS.openram_temp
write_lvs_script(cell_name, gds_name, sp_name, final_verification)
(outfile, errfile, resultsfile) = run_script(cell_name, "lvs")
@ -358,10 +359,10 @@ def run_pex(name, gds_name, sp_name, output=None, final_verification=False, outp
global num_pex_runs
num_pex_runs += 1
if not output_path:
output_path = OPTS.openram_temp
os.chdir(output_path)
if not output_path:

Binary file not shown.

View File

@ -0,0 +1,431 @@
<?xml version="1.0" encoding="utf-8"?>
<klayout-macro>
<description/>
<version/>
<category>drc</category>
<prolog/>
<epilog/>
<doc/>
<autorun>false</autorun>
<autorun-early>false</autorun-early>
<shortcut/>
<show-in-menu>true</show-in-menu>
<group-name>drc_scripts</group-name>
<menu-path>tools_menu.drc.end</menu-path>
<interpreter>dsl</interpreter>
<dsl-interpreter-name>drc-dsl-xml</dsl-interpreter-name>
<text>
#
# DRC for FreePDK45 according to :
# https://www.eda.ncsu.edu/wiki/FreePDK45:RuleDevel
# https://www.eda.ncsu.edu/wiki/FreePDK45:Contents
#
##########################################################################################
tstart = Time.now
# optionnal for a batch launch : klayout -b r drc_FreePDK45.lydrc -rd input=my_layout.gds -rd topcell=your_topcell -rd output=drc_FreePDK45.lyrdb
if $input
if $topcell
source($input,$topcell)
else
source($input)
end
end
if $output
report("FreePDK45 DRC runset", $output)
else
report("FreePDK45 DRC runset", "FreePDK45_DRC.lyrdb")
end
# DRC test to run or not
###############
OFFGRID = true
ANTENNA = true
DRC = true
# KLAYOUT setup
########################
# Use a tile size of 1mm
# tiles(100.um)
# Use a tile border of 10 micron:
# tile_borders(1.um)
# no_borders
# Hierachical
deep
# Use 4 CPU cores
threads(4)
verbose(true)
# layers definitions
########################
active = polygons(1, 0)
pwell = polygons(2, 0)
nwell = polygons(3, 0)
nplus = polygons(4, 0)
pplus = polygons(5, 0)
vtg = polygons(6, 0)
vth = polygons(7, 0)
thkox = polygons(8, 0)
poly = polygons(9, 0)
cont = polygons(10, 0)
metal1 = polygons(11, 0)
via1 = polygons(12, 0)
metal2 = polygons(13, 0)
via2 = polygons(14, 0)
metal3 = polygons(15, 0)
via3 = polygons(16, 0)
metal4 = polygons(17, 0)
via4 = polygons(18, 0)
metal5 = polygons(19, 0)
via5 = polygons(20, 0)
metal6 = polygons(21, 0)
via6 = polygons(22, 0)
metal7 = polygons(23, 0)
via7 = polygons(24, 0)
metal8 = polygons(25, 0)
via8 = polygons(26, 0)
metal9 = polygons(27, 0)
via9 = polygons(28, 0)
metal10 = polygons(29, 0)
# Computed layers
well = nwell.or(pwell)
gate = poly &amp; active
implant = nplus.or(pplus)
if DRC
# DRC section
########################
info("DRC section")
# splits a layer classes with increasing min dimensions
def classify_by_width(layer, *dimensions)
dimensions.collect { |d| layer = layer.sized(-0.5 * (d - 1.dbu)).sized(0.5 * (d - 1.dbu)) }
end
# Wells
nwell.and(pwell).output("WELL.1", "WELL.1 : nwell/pwell must not overlap")
# the rule "WELL.2 : Minimum spacing of well at different potential : 225nm" was not coded : see : https://www.klayout.de/forum/discussion/comment/6021
nwell.space(135.nm, euclidian).output("WELL.3", "WELL.3 : Minimum spacing of nwell at same potential : 135nm")
pwell.space(135.nm, euclidian).output("WELL.3", "WELL.3 : Minimum spacing of pwell at same potential : 135nm")
well.separation(well, 200.nm, euclidian).output("WELL.4", "WELL.4 : Minimum width of nwell/pwell : 200nm")
vtg.not(well).output("VT.1","VT.1 : Vtg adjust layers must coincide with well")
vth.not(well).output("VT.1","VT.1 : Vth adjust layers must coincide with well")
# Poly
poly.width(50.nm, euclidian).output("POLY.1", "POLY.1 : Minimum width of poly : 50nm")
poly_sep_active = poly.separation(active, 140.nm, projection)
if poly_sep_active.polygons?
poly_sep_active.polygons.without_area(0).output("POLY.2", "POLY.2 : Minimum spacing of poly AND active: 140nm")
end
poly_sep_active.forget
poly.enclosing(gate, 55.nm, projection).polygons.without_area(0).output("POLY.3", "POLY.3 : Minimum poly extension beyond active : 55nm")
active.enclosing(gate, 70.nm, projection).polygons.without_area(0).output("POLY.4", "POLY.4 : Minimum enclosure of active around gate : 70nm")
poly.not(active).separation(active, 50.nm, projection).polygons.without_area(0).output("POLY.5", "POLY.5 : Minimum spacing of field poly to active: 50nm")
poly.space(75.nm, euclidian).output("POLY.6", "POLY.6 : Minimum spacing of field poly: 75nm")
# Active
active.width(90.nm, euclidian).output("ACTIVE.1", "ACTIVE.1 : Minimum width of active : 90nm")
active.space(80.nm, euclidian).output("ACTIVE.2", "ACTIVE.2 : Minimum spacing of active : 80nm")
well.enclosing(active, 55.nm, euclidian).output("ACTIVE.3", "ACTIVE.3 : Minimum enclosure/spacing of nwell/pwell to active: 55nm")
active.not(well).output("ACTIVE.4", "ACTIVE.4 : active must be inside nwell or pwell")
# Implant
implant.separation(gate, 70.nm, projection).polygons.without_area(0).output("IMPLANT.1", "IMPLANT.1 : Minimum spacing of nimplant/ pimplant to channel : 70nm")
implant.separation(cont, 25.nm, projection).polygons.without_area(0).output("IMPLANT.2", "IMPLANT.1 : Minimum spacing of nimplant/ pimplant to contact : 25nm")
implant.width(45.nm, euclidian).output("IMPLANT.3", "IMPLANT.3 : Minimum width of nimplant/ pimplant : 45nm")
implant.space(45.nm, euclidian).output("IMPLANT.4", "IMPLANT.4 : Minimum spacing of nimplant/ pimplant : 45nm")
nplus.and(pplus).output("IMPLANT.5", "IMPLANT.5 : Nimplant and pimplant must not overlap")
implant.forget
# Contact
cont.edges.without_length(65.nm).output("CONTACT.1", "CONTACT.1 : Minimum/Maximum width of contact : 65nm")
cont.space(75.nm, euclidian).output("CONTACT.2", "CONTACT.2 : Minimum spacing of contact : 75nm")
cont.not(active).not(poly).not(metal1).output("CONTACT.3", "CONTACT.3 : contact must be inside active or poly or metal1")
active.enclosing(cont, 5.nm, euclidian).output("CONTACT.4", "CONTACT.4 : Minimum enclosure of active around contact : 5nm")
poly.enclosing(cont, 5.nm, euclidian).output("CONTACT.5", "CONTACT.5 : Minimum enclosure of poly around contact : 5nm")
cont.separation(poly, 35.nm, euclidian).output("CONTACT.6", "CONTACT.6 : Minimum spacing of contact and poly : 35nm")
# Metal1
metal1.width(65.nm, euclidian).output("METAL1.1", "METAL1.1 : Minimum width of metal1 : 65nm")
metal1.space(65.nm, euclidian).output("METAL1.2", "METAL1.2 : Minimum spacing of metal1 : 65nm")
cont_edges_with_less_enclosure = metal1.enclosing(cont, 35.nm, projection).second_edges
error_corners = cont_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu)
cont_edges_with_less_enclosure.forget
cont.interacting(error_corners.polygons(1.dbu)).output("METAL1.3", "METAL1.3 : Minimum enclosure around contact on two opposite sides : 35nm")
error_corners.forget
via1_edges_with_less_enclosure = metal1.enclosing(via1, 35.nm, projection).second_edges
error_corners = via1_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu)
via1_edges_with_less_enclosure.forget
via1.interacting(error_corners.polygons(1.dbu)).output("METAL1.4", "METAL1.4 : Minimum enclosure around via1 on two opposite sides : 35nm")
error_corners.forget
metal1_gt90, metal1_gt270, metal1_gt500, metal1_gt900, metal1_gt1500 = classify_by_width(metal1, 90.nm, 270.nm, 500.nm, 900.nm, 1500.nm)
metal1_gt90.edges.with_length(300.nm,nil).space(90.nm,euclidian).output("METAL1.5", "METAL1.5 : Minimum spacing of metal1 wider than 90 nm and longer than 300 nm : 90nm")
metal1_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL1.6", "METAL1.6 : Minimum spacing of metal1 wider than 270 nm and longer than 900 nm : 270nm")
metal1_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL1.7", "METAL1.7 : Minimum spacing of metal1 wider than 500 nm and longer than 1.8 um : 500nm")
metal1_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL1.8", "METAL1.8 : Minimum spacing of metal1 wider than 900 nm and longer than 2.7 um : 900nm")
metal1_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL1.9", "METAL1.9 : Minimum spacing of metal1 wider than 1500 nm and longer than 4.0 um : 1500nm")
[ metal1_gt90, metal1_gt270, metal1_gt500, metal1_gt900, metal1_gt1500 ].each { |l| l.forget }
# Via1
via1.edges.without_length(65.nm).output("VIA1.1", "VIA1.1 : Minimum/Maximum width of via1 : 65nm")
via1.space(75.nm, euclidian).output("VIA1.2", "VIA1.2 : Minimum spacing of via1 : 75nm")
via1.not(metal1).output("VIA1.3", "VIA1.3 : via1 must be inside metal1")
via1.not(metal2).output("VIA1.4", "VIA1.4 : via1 must be inside metal2")
# metal2
metal2.width(70.nm, euclidian).output("METAL2.1", "METAL2.1 : Minimum width of intermediate metal2 : 70nm")
metal2.space(70.nm, euclidian).output("METAL2.2", "METAL2.2 : Minimum spacing of intermediate metal2 : 70nm")
via1_edges_with_less_enclosure = metal2.enclosing(via1, 35.nm, projection).second_edges
error_corners = via1_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu)
via1_edges_with_less_enclosure.forget
via1.interacting(error_corners.polygons(1.dbu)).output("METAL2.3", "METAL2.3 : Minimum enclosure around via1 on two opposite sides : 35nm")
error_corners.forget
via2_edges_with_less_enclosure = metal2.enclosing(via2, 35.nm, projection).second_edges
error_corners = via2_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu)
via2_edges_with_less_enclosure.forget
via2.interacting(error_corners.polygons(1.dbu)).output("METAL2.4", "METAL2.4 : Minimum enclosure around via2 on two opposite sides : 35nm")
error_corners.forget
metal2_gt90, metal2_gt270, metal2_gt500, metal2_gt900, metal2_gt1500 = classify_by_width(metal2, 90.nm, 270.nm, 500.nm, 900.nm, 1500.nm)
metal2_gt90.edges.with_length(300.nm,nil).space(90.nm,euclidian).output("METAL2.5", "METAL2.5 : Minimum spacing of intermediate metal2 wider than 90 nm and longer than 300 nm : 90nm")
metal2_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL2.6", "METAL2.6 : Minimum spacing of intermediate metal2 wider than 270 nm and longer than 900 nm : 270nm")
metal2_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL2.7", "METAL2.7 : Minimum spacing of intermediate metal2 wider than 500 nm and longer than 1.8 um : 500nm")
metal2_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL2.8", "METAL2.8 : Minimum spacing of intermediate metal2 wider than 900 nm and longer than 2.7 um : 900nm")
metal2_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL2.9", "METAL2.9 : Minimum spacing of intermediate metal2 wider than 1500 nm and longer than 4.0 um : 1500nm")
[ metal2_gt90, metal2_gt270, metal2_gt500, metal2_gt900, metal2_gt1500 ].each { |l| l.forget }
# via2
# FreePDK Calibre deck incorrectly has this as 65nm so we are going to be compatible.
via2.edges.without_length(65.nm).output("VIA2.1", "VIA2.1 : Minimum/Maximum width of via2 : 65nm")
via2.space(85.nm, euclidian).output("VIA2.2", "VIA2.2 : Minimum spacing of via2 : 85nm")
via2.not(metal2).output("VIA2.3", "VIA2.3 : via2 must be inside metal2")
via2.not(metal3).output("VIA2.4", "VIA2.4 : via2 must be inside metal3")
# metal3
metal3.width(70.nm, euclidian).output("METAL3.1", "METAL3.1 : Minimum width of intermediate metal3 : 70nm")
metal3.space(70.nm, euclidian).output("METAL3.2", "METAL3.2 : Minimum spacing of intermediate metal3 : 70nm")
via2_edges_with_less_enclosure = metal3.enclosing(via2, 35.nm, projection).second_edges
error_corners = via2_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu)
via2_edges_with_less_enclosure.forget
via2.interacting(error_corners.polygons(1.dbu)).output("METAL3.3", "METAL3.3 : Minimum enclosure around via2 on two opposite sides : 35nm")
error_corners.forget
via3_edges_with_less_enclosure = metal3.enclosing(via3, 35.nm, projection).second_edges
error_corners = via3_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu)
via3_edges_with_less_enclosure.forget
via3.interacting(error_corners.polygons(1.dbu)).output("METAL3.4", "METAL3.4 : Minimum enclosure around via3 on two opposite sides : 35nm")
error_corners.forget
metal3_gt90, metal3_gt270, metal3_gt500, metal3_gt900, metal3_gt1500 = classify_by_width(metal3, 90.nm, 270.nm, 500.nm, 900.nm, 1500.nm)
metal3_gt90.edges.with_length(300.nm,nil).space(90.nm,euclidian).output("METAL3.5", "METAL3.5 : Minimum spacing of intermediate metal3 wider than 90 nm and longer than 300 nm : 90nm")
metal3_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL3.6", "METAL3.6 : Minimum spacing of intermediate metal3 wider than 270 nm and longer than 900 nm : 270nm")
metal3_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL3.7", "METAL3.7 : Minimum spacing of intermediate metal3 wider than 500 nm and longer than 1.8 um : 500nm")
metal3_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL3.8", "METAL3.8 : Minimum spacing of intermediate metal3 wider than 900 nm and longer than 2.7 um : 900nm")
metal3_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL3.9", "METAL3.9 : Minimum spacing of intermediate metal3 wider than 1500 nm and longer than 4.0 um : 1500nm")
[ metal3_gt90, metal3_gt270, metal3_gt500, metal3_gt900, metal3_gt1500 ].each { |l| l.forget }
# via3
via3.edges.without_length(70.nm).output("VIA3.1", "VIA3.1 : Minimum/Maximum width of via3 : 70nm")
via3.space(85.nm, euclidian).output("VIA3.2", "VIA3.2 : Minimum spacing of via3 : 85nm")
via3.not(metal3).output("VIA3.3", "VIA3.3 : via3 must be inside metal3")
via3.not(metal4).output("VIA3.4", "VIA3.4 : via3 must be inside metal4")
# metal4
metal4.width(140.nm, euclidian).output("METAL4.1", "METAL4.1 : Minimum width of semi-global metal4 : 140nm")
metal4.space(140.nm, euclidian).output("METAL4.2", "METAL4.2 : Minimum spacing of semi-global metal4 : 140nm")
metal4_gt270, metal4_gt500, metal4_gt900 = classify_by_width(metal4, 270.nm, 500.nm, 900.nm)
metal4_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL4.6", "METAL4.6 : Minimum spacing of semi-global metal4 wider than 270 nm and longer than 900 nm : 270nm")
metal4_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL4.7", "METAL4.7 : Minimum spacing of semi-global metal4 wider than 500 nm and longer than 1.8 um : 500nm")
metal4_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL4.8", "METAL4.8 : Minimum spacing of semi-global meta4l wider than 900 nm and longer than 2.7 um : 900nm")
[ metal4_gt270, metal4_gt500, metal4_gt900 ].each { |l| l.forget }
# via4
via4.edges.without_length(140.nm).output("VIA4.1", "VIA4.1 : Minimum/Maximum width of via4 : 140nm")
via4.space(160.nm, euclidian).output("VIA4.2", "VIA4.2 : Minimum spacing of via4 : 160nm")
via4.not(metal4).output("VIA4.3", "VIA4.3 : via4 must be inside metal4")
via4.not(metal5).output("VIA4.4", "VIA4.4 : via4 must be inside metal5")
# metal5
metal5.width(140.nm, euclidian).output("METAL5.1", "METAL5.1 : Minimum width of semi-global metal5 : 140nm")
metal5.space(140.nm, euclidian).output("METAL5.2", "METAL5.2 : Minimum spacing of semi-global metal5 : 140nm")
metal5_gt270, metal5_gt500, metal5_gt900 = classify_by_width(metal5, 270.nm, 500.nm, 900.nm)
metal5_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL5.6", "METAL5.6 : Minimum spacing of semi-global metal5 wider than 270 nm and longer than 900 nm : 270nm")
metal5_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL5.7", "METAL5.7 : Minimum spacing of semi-global metal5 wider than 500 nm and longer than 1.8 um : 500nm")
metal5_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL5.8", "METAL5.8 : Minimum spacing of semi-global meta5l wider than 900 nm and longer than 2.7 um : 900nm")
[ metal5_gt270, metal5_gt500, metal5_gt900 ].each { |l| l.forget }
# via5
via5.edges.without_length(140.nm).output("VIA5.1", "VIA5.1 : Minimum/Maximum width of via5 : 140nm")
via5.space(160.nm, euclidian).output("VIA5.2", "VIA5.2 : Minimum spacing of via5 : 160nm")
via5.not(metal5).output("VIA5.3", "VIA5.3 : via5 must be inside metal5")
via5.not(metal6).output("VIA5.4", "VIA5.4 : via5 must be inside metal6")
# metal6
metal6.width(140.nm, euclidian).output("METAL6.1", "METAL6.1 : Minimum width of semi-global metal6 : 140nm")
metal6.space(140.nm, euclidian).output("METAL6.2", "METAL6.2 : Minimum spacing of semi-global metal6 : 140nm")
metal6_gt270, metal6_gt500, metal6_gt900 = classify_by_width(metal6, 270.nm, 500.nm, 900.nm)
metal6_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL6.6", "METAL6.6 : Minimum spacing of semi-global metal6 wider than 270 nm and longer than 900 nm : 270nm")
metal6_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL6.7", "METAL6.7 : Minimum spacing of semi-global metal6 wider than 500 nm and longer than 1.8 um : 500nm")
metal6_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL6.8", "METAL6.8 : Minimum spacing of semi-global metal6 wider than 900 nm and longer than 2.7 um : 900nm")
[ metal6_gt270, metal6_gt500, metal6_gt900 ].each { |l| l.forget }
# via6
via6.edges.without_length(140.nm).output("VIA6.1", "VIA6.1 : Minimum/Maximum width of via6 : 140nm")
via6.space(160.nm, euclidian).output("VIA6.2", "VIA6.2 : Minimum spacing of via6 : 160nm")
via6.not(metal6).output("VIA6.3", "VIA6.3 : via6 must be inside metal6")
via6.not(metal7).output("VIA6.4", "VIA6.4 : via6 must be inside metal7")
# metal7
metal7.width(400.nm, euclidian).output("METAL7.1", "METAL7.1 : Minimum width of thin global metal7 : 400nm")
metal7.space(400.nm, euclidian).output("METAL7.2", "METAL7.2 : Minimum spacing of thin global metal7 : 400nm")
metal7_gt500, metal7_gt900, metal7_gt1500 = classify_by_width(metal7, 500.nm, 900.nm, 1500.nm)
metal7_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL7.7", "METAL7.7 : Minimum spacing of thin global metal7 wider than 500 nm and longer than 1.8 um : 500nm")
metal7_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL7.8", "METAL7.8 : Minimum spacing of thin global metal7 wider than 900 nm and longer than 2.7 um : 900nm")
metal7_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL7.9", "METAL7.9 : Minimum spacing of thin global meta7l wider than 1500 nm and longer than 4.0 um : 1500nm")
[ metal7_gt500, metal7_gt900, metal7_gt1500 ].each { |l| l.forget }
# via7
via7.edges.without_length(400.nm).output("VIA6.1", "VIA6.1 : Minimum/Maximum width of via7 : 400nm")
via7.space(440.nm, euclidian).output("VIA6.2", "VIA6.2 : Minimum spacing of via7 : 440nm")
via7.not(metal7).output("VIA7.3", "VIA7.3 : via7 must be inside metal7")
via7.not(metal8).output("VIA7.4", "VIA7.4 : via7 must be inside metal8")
# metal8
metal8.width(400.nm, euclidian).output("METAL8.1", "METAL8.1 : Minimum width of thin global metal8 : 400nm")
metal8.space(400.nm, euclidian).output("METAL8.2", "METAL8.2 : Minimum spacing of thin global metal8 : 400nm")
metal8_gt500, metal8_gt900, metal8_gt1500 = classify_by_width(metal8, 500.nm, 900.nm, 1500.nm)
metal8_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL8.7", "METAL8.7 : Minimum spacing of thin global metal8 wider than 500 nm and longer than 1.8 um : 500nm")
metal8_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL8.8", "METAL8.8 : Minimum spacing of thin global metal8 wider than 900 nm and longer than 2.7 um : 900nm")
metal8_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL8.9", "METAL8.9 : Minimum spacing of thin global metal8 wider than 1500 nm and longer than 4.0 um : 1500nm")
[ metal8_gt500, metal8_gt900, metal8_gt1500 ].each { |l| l.forget }
# via8
via8.edges.without_length(400.nm).output("VIA8.1", "VIA8.1 : Minimum/Maximum width of via8 : 400nm")
via8.space(440.nm, euclidian).output("VIA8.2", "VIA8.2 : Minimum spacing of via8 : 440nm")
via8.not(metal8).output("VIA8.3", "VIA8.3 : via8 must be inside metal8")
via8.not(metal9).output("VIA8.4", "VIA8.4 : via8 must be inside metal9")
# metal9
metal9.width(800.nm, euclidian).output("METAL9.1", "METAL9.1 : Minimum width of global metal9 : 800nm")
metal9.space(800.nm, euclidian).output("METAL9.2", "METAL9.2 : Minimum spacing of global metal9 : 800nm")
metal9_gt500, metal9_gt900, metal9_gt1500 = classify_by_width(metal9, 500.nm, 900.nm, 1500.nm)
metal9_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL9.7", "METAL9.7 : Minimum spacing of global metal9 wider than 500 nm and longer than 1.8 um : 500nm")
metal9_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL9.8", "METAL9.8 : Minimum spacing of global metal9 wider than 900 nm and longer than 2.7 um : 900nm")
metal9_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL9.9", "METAL9.9 : Minimum spacing of global metal9 wider than 1500 nm and longer than 4.0 um : 1500nm")
[ metal9_gt500, metal9_gt900, metal9_gt1500 ].each { |l| l.forget }
# via9
via9.edges.without_length(800.nm).output("VIA9.1", "VIA9.1 : Minimum/Maximum width of via9 : 800nm")
via9.space(880.nm, euclidian).output("VIA9.2", "VIA9.2 : Minimum spacing of via9 : 880nm")
via9.not(metal9).output("VIA9.3", "VIA9.3 : via9 must be inside metal9")
via9.not(metal10).output("VIA9.4", "VIA9.4 : via9 must be inside metal10")
# metal10
metal10.width(800.nm, euclidian).output("METAL10.1", "METAL10.1 : Minimum width of global metal10 : 800nm")
metal10.space(800.nm, euclidian).output("METAL10.2", "METAL10.2 : Minimum spacing of global metal10 : 800nm")
metal10_gt500, metal10_gt900, metal10_gt1500 = classify_by_width(metal10, 500.nm, 900.nm, 1500.nm)
metal10_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL10.7", "METAL10.7 : Minimum spacing of global metal10 wider than 500 nm and longer than 1.8 um : 500nm")
metal10_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL10.8", "METAL10.8 : Minimum spacing of global metal10 wider than 900 nm and longer than 2.7 um : 900nm")
metal10_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL10.9", "METAL10.9 : Minimum spacing of global metal10 wider than 1500 nm and longer than 4.0 um : 1500nm")
[ metal10_gt500, metal10_gt900, metal10_gt1500 ].each { |l| l.forget }
end
# ONGRID also defined in :
# https://www.eda.ncsu.edu/wiki/FreePDK45:Manufacturing_Grid
###########################################
if OFFGRID
info("GRID section")
grid = 2.5.nm
all_drawing = [ :well, :active, :vtg, :vth, :pplus, :nplus, :poly, :thkox, :cont, :metal1, :via1, :metal2, :via2, :metal3, :via3, :metal4, :via4, :metal5, :via5, :metal6, :via6, :metal7, :via7, :metal8, :via8, :metal9, :via9, :metal10 ]
all_drawing.each do |dwg|
# a Ruby idiom to get the value of a variable whose name is in "dwg" (as symbol)
layer = binding.local_variable_get(dwg)
r_grid = layer.ongrid(grid).polygons(10.nm)
r_grid.output("GRID: vertexes on layer #{dwg} not on grid of #{'%.12g' % grid}")
end
end
# ANTENNA checks
################
if ANTENNA
info("ANTENNA section")
diode = nplus &amp; active - nwell # diode recognition layer
# build connction of poly+gate to metal1
connect(gate, poly)
connect(poly, cont)
connect(diode, cont)
connect(cont, metal1)
antenna_check(gate, metal1, 300.0, diode).output("METAL1_ANTENNA", "METAL1_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1")
# build connction of poly+gate to metal2
connect(metal1, via1)
connect(via1, metal2)
antenna_check(gate, metal2, 300.0, diode).output("METAL2_ANTENNA", "METAL2_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1")
# build connction of poly+gate to metal3
connect(metal2, via2)
connect(via2, metal3)
antenna_check(gate, metal3, 300.0, diode).output("METAL3_ANTENNA", "METAL3_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1")
# build connction of poly+gate to metal4
connect(metal3, via3)
connect(via3, metal4)
antenna_check(gate, metal4, 300.0, diode).output("METAL4_ANTENNA", "METAL4_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1")
# build connction of poly+gate to metal5
connect(metal4, via4)
connect(via4, metal5)
antenna_check(gate, metal5, 300.0, diode).output("METAL5_ANTENNA", "METAL5_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1")
# build connction of poly+gate to metal6
connect(metal5, via5)
connect(via5, metal6)
antenna_check(gate, metal6, 300.0, diode).output("METAL6_ANTENNA", "METAL6_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1")
# build connction of poly+gate to metal7
connect(metal6, via6)
connect(via6, metal7)
antenna_check(gate, metal7, 300.0, diode).output("METAL7_ANTENNA", "METAL7_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1")
# build connction of poly+gate to metal8
connect(metal7, via7)
connect(via7, metal8)
antenna_check(gate, metal8, 300.0, diode).output("METAL8_ANTENNA", "METAL8_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1")
# build connction of poly+gate to metal9
connect(metal8, via8)
connect(via8, metal9)
antenna_check(gate, metal9, 300.0, diode).output("METAL9_ANTENNA", "METAL9_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1")
# build connction of poly+gate to metal10
connect(metal9, via9)
connect(via9, metal10)
antenna_check(gate, metal10, 300.0, diode).output("METAL10_ANTENNA", "METAL10_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1")
end
# time spent for the DRC
time = Time.now
hours = ((time - tstart)/3600).to_i
minutes = ((time - tstart)/60 - hours * 60).to_i
seconds = ((time - tstart) - (minutes * 60 + hours * 3600)).to_i
$stdout.write "DRC finished at : #{time.hour}:#{time.min}:#{time.sec} - DRC duration = #{hours} hrs. #{minutes} min. #{seconds} sec.\n"
</text>
</klayout-macro>

View File

@ -0,0 +1,269 @@
<?xml version="1.0" encoding="utf-8"?>
<klayout-macro>
<description/>
<version/>
<category>lvs</category>
<prolog/>
<epilog/>
<doc/>
<autorun>false</autorun>
<autorun-early>false</autorun-early>
<shortcut/>
<show-in-menu>true</show-in-menu>
<group-name>lvs_scripts</group-name>
<menu-path>tools_menu.lvs.end</menu-path>
<interpreter>dsl</interpreter>
<dsl-interpreter-name>lvs-dsl-xml</dsl-interpreter-name>
<text>#
# Extraction for freePDK45
#
############################
tstart = Time.now
# optionnal for a batch launch : klayout -b -rd input=my_layout.gds -rd report=my_report.lyrdb -rd schematic=reference_netlist.cir -rd target_netlist=extracted_netlist.cir -r lvs_freepdk45.lvs
if $input
source($input)
end
if $report
report_lvs($report)
else
report_lvs("lvs_report.lvsdb")
end
if $schematic
#reference netlist
schematic($schematic)
else
schematic(RBA::CellView::active.filename.sub(/\.(oas|gds|oas.gz|gds.gz)$/, ".sp"))
end
# true: use net names instead of numbers
# false: use numbers for nets
spice_with_net_names = true
# true: put in comments with details
# false: no comments
spice_with_comments = true
if $target_netlist
target_netlist($target_netlist)
else
# target_netlist("netlist.cir", write_spice(spice_with_net_names, spice_with_comments), "The netlist comment goes here.")
target_netlist(File.join(File.dirname(RBA::CellView::active.filename), source.cell_name+"_extracted.cir"), write_spice(spice_with_net_names, spice_with_comments), "Extracted by KLayout on : #{Time.now.strftime("%d/%m/%Y %H:%M")}")
end
# Hierarchical mode
deep
# Use 4 CPU cores
threads(4)
# Print details
verbose(true)
# layers definitions
########################
active = input(1, 0)
pwell = input(2, 0)
nwell = input(3, 0)
nplus = input(4, 0)
pplus = input(5, 0)
vtg = input(6, 0)
vth = input(7, 0)
thkox = input(8, 0)
poly = input(9, 0)
cont = input(10, 0)
metal1 = input(11, 0)
metal1_lbl = input(11, 1)
metal1_pin = input(11, 2)
via1 = input(12, 0)
metal2 = input(13, 0)
metal2_lbl = input(13, 1)
metal2_pin = input(13, 2)
via2 = input(14, 0)
metal3 = input(15, 0)
metal3_lbl = input(15, 1)
metal3_pin = input(15, 2)
via3 = input(16, 0)
metal4 = input(17, 0)
metal4_lbl = input(17, 1)
metal4_pin = input(17, 2)
via4 = input(18, 0)
metal5 = input(19, 0)
metal5_lbl = input(19, 1)
metal5_pin = input(19, 2)
via5 = input(20, 0)
metal6 = input(21, 0)
metal6_lbl = input(21, 1)
metal6_pin = input(21, 2)
via6 = input(22, 0)
metal7 = input(23, 0)
metal7_lbl = input(23, 1)
metal7_pin = input(23, 2)
via7 = input(24, 0)
metal8 = input(25, 0)
metal8_lbl = input(25, 1)
metal8_pin = input(25, 2)
via8 = input(26, 0)
metal9 = input(27, 0)
metal9_lbl = input(27, 1)
metal9_pin = input(27, 2)
via9 = input(28, 0)
metal10 = input(29, 0)
metal10_lbl = input(29, 1)
metal10_pin = input(29, 2)
# Bulk layer for terminal provisioning
bulk = polygon_layer
# Computed layers
active_in_nwell = active &amp; nwell
pactive = active_in_nwell &amp; pplus
ntie = active_in_nwell &amp; nplus
pgate = pactive &amp; poly
psd = pactive - pgate
lv_pgate = pgate - vtg - thkox
gv_pgate = pgate &amp; vtg - vth - thkox
hv_pgate = pgate - vtg - vth &amp; thkox
active_in_pwell = active &amp; pwell
nactive = active_in_pwell &amp; nplus
ptie = active_in_pwell &amp; pplus
ngate = nactive &amp; poly
nsd = nactive - ngate
lv_ngate = ngate - vtg - thkox
gv_ngate = ngate &amp; vtg - vth - thkox
hv_ngate = ngate - vtg - vth &amp; thkox
cheat("cell_6t", "dummy_cell_6t", "cell_1rw", "dummy_cell_1rw", "cell_2rw", "dummy_cell_2rw", "dff","wordline_driver_0") {
# PMOS transistor device extraction
extract_devices(mos4("PMOS_VTL"), { "SD" =&gt; psd, "G" =&gt; lv_pgate, "tS" =&gt; psd, "tD" =&gt; psd, "tG" =&gt; poly, "W" =&gt; nwell })
extract_devices(mos4("PMOS_VTG"), { "SD" =&gt; psd, "G" =&gt; gv_pgate, "tS" =&gt; psd, "tD" =&gt; psd, "tG" =&gt; poly, "W" =&gt; nwell })
extract_devices(mos4("PMOS_VTH"), { "SD" =&gt; psd, "G" =&gt; hv_pgate, "tS" =&gt; psd, "tD" =&gt; psd, "tG" =&gt; poly, "W" =&gt; nwell })
# NMOS transistor device extraction
extract_devices(mos4("NMOS_VTL"), { "SD" =&gt; nsd, "G" =&gt; lv_ngate, "tS" =&gt; nsd, "tD" =&gt; nsd, "tG" =&gt; poly, "W" =&gt; pwell })
extract_devices(mos4("NMOS_VTG"), { "SD" =&gt; nsd, "G" =&gt; gv_ngate, "tS" =&gt; nsd, "tD" =&gt; nsd, "tG" =&gt; poly, "W" =&gt; pwell })
extract_devices(mos4("NMOS_VTH"), { "SD" =&gt; nsd, "G" =&gt; hv_ngate, "tS" =&gt; nsd, "tD" =&gt; nsd, "tG" =&gt; poly, "W" =&gt; pwell })
}
# Define connectivity for netlist extraction
# Inter-layer
connect(nwell, ntie)
connect(pwell, ptie)
connect(cont, ntie)
connect(cont, ptie)
connect(psd, cont)
connect(nsd, cont)
connect(poly, cont)
connect(cont, metal1)
connect(cont, metal1)
connect(metal1, via1)
connect(via1, metal2)
connect(metal2, via2)
connect(via2, metal3)
connect(metal3, via3)
connect(via3, metal4)
connect(metal4, via4)
connect(via4, metal5)
connect(metal5, via5)
connect(via5, metal6)
connect(metal6, via6)
connect(via6, metal7)
connect(metal7, via7)
connect(via7, metal8)
connect(metal8, via8)
connect(via8, metal9)
connect(metal9, via9)
connect(via9, metal10)
# attach labels :
connect(metal1, metal1_lbl)
connect(metal1, metal1_pin)
connect(metal2, metal2_lbl)
connect(metal2, metal2_pin)
connect(metal3, metal3_lbl)
connect(metal3, metal3_pin)
connect(metal4, metal4_lbl)
connect(metal4, metal4_pin)
connect(metal5, metal5_lbl)
connect(metal5, metal5_pin)
connect(metal6, metal6_lbl)
connect(metal6, metal6_pin)
connect(metal7, metal7_lbl)
connect(metal7, metal7_pin)
connect(metal8, metal8_lbl)
connect(metal8, metal8_pin)
connect(metal9, metal9_lbl)
connect(metal9, metal9_pin)
connect(metal10, metal10_lbl)
connect(metal10, metal10_pin)
# Global
schematic.simplify
connect_global(pwell, "PWELL")
connect_global(nwell, "NWELL")
connect_global(bulk, "BULK")
#for pat in %w(pnand*_0 and2_dec_0 port_address* replica_bitcell_array)
# connect_explicit(pat, [ "NWELL", "vdd" ])
# connect_explicit(pat, [ "BULK", "PWELL", "gnd" ])
#end
#for pat in %w(XOR* XNOR* TLAT* TINV* TBUF* SDFF* OR* OAI* NOR* NAND* MUX* LOGIC* INV* HA* FILLCELL*
# FA* DLL* DLH* DFF* DFFS* DFFR* DFFRS* CLKGATE* CLKBUF* BUF* AOI* ANTENNA* AND*)
# connect_explicit(pat, [ "NWELL", "VDD" ])
# connect_explicit(pat, [ "BULK", "VSS" ])
#end
# Actually performs the extraction
netlist # ... not really required
# Flatten cells which are present in one netlist only
align
# SIMPLIFICATION of the netlist
#netlist.make_top_level_pins
#netlist.combine_devices
#netlist.purge
#netlist.purge_nets
netlist.simplify
# Tolerances for the devices extracted parameters
# tolerance(device_class_name, parameter_name [, :absolute =&gt; absolute_tolerance] [, :relative =&gt; relative_tolerance])
tolerance("PMOS_LVT", "W", :absolute =&gt; 1.nm, :relative =&gt; 0.001)
tolerance("PMOS_LVT", "L", :absolute =&gt; 1.nm, :relative =&gt; 0.001)
tolerance("PMOS_GVT", "W", :absolute =&gt; 1.nm, :relative =&gt; 0.001)
tolerance("PMOS_GVT", "L", :absolute =&gt; 1.nm, :relative =&gt; 0.001)
tolerance("PMOS_HVT", "W", :absolute =&gt; 1.nm, :relative =&gt; 0.001)
tolerance("PMOS_HVT", "L", :absolute =&gt; 1.nm, :relative =&gt; 0.001)
tolerance("NMOS_LVT", "W", :absolute =&gt; 1.nm, :relative =&gt; 0.001)
tolerance("NMOS_LVT", "L", :absolute =&gt; 1.nm, :relative =&gt; 0.001)
tolerance("NMOS_GVT", "W", :absolute =&gt; 1.nm, :relative =&gt; 0.001)
tolerance("NMOS_GVT", "L", :absolute =&gt; 1.nm, :relative =&gt; 0.001)
tolerance("NMOS_HVT", "W", :absolute =&gt; 1.nm, :relative =&gt; 0.001)
tolerance("NMOS_HVT", "L", :absolute =&gt; 1.nm, :relative =&gt; 0.001)
#max_res(1000000)
#min_caps(1e-15)
max_branch_complexity(65536)
max_depth(16)
if ! compare
#raise "ERROR : Netlists don't match"
puts "ERROR : Netlists don't match"
else
puts "CONGRATULATIONS! Netlists match."
end
# time spent for the LVS
time = Time.now
hours = ((time - tstart)/3600).to_i
minutes = ((time - tstart)/60 - hours * 60).to_i
seconds = ((time - tstart) - (minutes * 60 + hours * 3600)).to_i
$stdout.write "LVS finished at : #{time.hour}:#{time.min}:#{time.sec} - LVS duration = #{hours} hrs. #{minutes} min. #{seconds} sec.\n"</text>
</klayout-macro>

View File

@ -0,0 +1,172 @@
<?xml version="1.0" encoding="utf-8"?>
<technology>
<name>FreePDK45</name>
<description>Free PDK 45nm</description>
<group/>
<dbu>0.001</dbu>
<base-path>$(appdata_path)/tech/FreePDK45</base-path>
<original-base-path>.klayout/tech/FreePDK45</original-base-path>
<layer-properties_file>FreePDK45.lyp</layer-properties_file>
<add-other-layers>true</add-other-layers>
<reader-options>
<gds2>
<box-mode>1</box-mode>
<allow-big-records>true</allow-big-records>
<allow-multi-xy-records>true</allow-multi-xy-records>
</gds2>
<common>
<create-other-layers>true</create-other-layers>
<layer-map>layer_map()</layer-map>
<enable-properties>true</enable-properties>
<enable-text-objects>true</enable-text-objects>
</common>
<lefdef>
<read-all-layers>true</read-all-layers>
<layer-map>layer_map()</layer-map>
<dbu>0.001</dbu>
<produce-net-names>true</produce-net-names>
<net-property-name>#1</net-property-name>
<produce-inst-names>true</produce-inst-names>
<inst-property-name>#1</inst-property-name>
<produce-pin-names>false</produce-pin-names>
<pin-property-name>#1</pin-property-name>
<produce-cell-outlines>true</produce-cell-outlines>
<cell-outline-layer>OUTLINE</cell-outline-layer>
<produce-placement-blockages>true</produce-placement-blockages>
<placement-blockage-layer>PLACEMENT_BLK</placement-blockage-layer>
<produce-regions>true</produce-regions>
<region-layer>REGIONS</region-layer>
<produce-via-geometry>true</produce-via-geometry>
<via-geometry-suffix/>
<via-geometry-datatype>0</via-geometry-datatype>
<produce-pins>true</produce-pins>
<pins-suffix>.PIN</pins-suffix>
<pins-datatype>2</pins-datatype>
<produce-obstructions>true</produce-obstructions>
<obstructions-suffix>.OBS</obstructions-suffix>
<obstructions-datatype>3</obstructions-datatype>
<produce-blockages>true</produce-blockages>
<blockages-suffix>.BLK</blockages-suffix>
<blockages-datatype>4</blockages-datatype>
<produce-labels>true</produce-labels>
<labels-suffix>.LABEL</labels-suffix>
<labels-datatype>1</labels-datatype>
<produce-routing>true</produce-routing>
<routing-suffix/>
<routing-datatype>0</routing-datatype>
</lefdef>
<mebes>
<invert>false</invert>
<subresolution>true</subresolution>
<produce-boundary>true</produce-boundary>
<num-stripes-per-cell>64</num-stripes-per-cell>
<num-shapes-per-cell>0</num-shapes-per-cell>
<data-layer>1</data-layer>
<data-datatype>0</data-datatype>
<data-name>DATA</data-name>
<boundary-layer>0</boundary-layer>
<boundary-datatype>0</boundary-datatype>
<boundary-name>BORDER</boundary-name>
<layer-map>layer_map()</layer-map>
<create-other-layers>true</create-other-layers>
</mebes>
<dxf>
<dbu>0.001</dbu>
<unit>1</unit>
<text-scaling>100</text-scaling>
<circle-points>100</circle-points>
<circle-accuracy>0</circle-accuracy>
<contour-accuracy>0</contour-accuracy>
<polyline-mode>0</polyline-mode>
<render-texts-as-polygons>false</render-texts-as-polygons>
<keep-other-cells>false</keep-other-cells>
<keep-layer-names>false</keep-layer-names>
<create-other-layers>true</create-other-layers>
<layer-map>layer_map()</layer-map>
</dxf>
<cif>
<wire-mode>0</wire-mode>
<dbu>0.001</dbu>
<layer-map>layer_map()</layer-map>
<create-other-layers>true</create-other-layers>
<keep-layer-names>false</keep-layer-names>
</cif>
<mag>
<lambda>1</lambda>
<dbu>0.001</dbu>
<layer-map>layer_map()</layer-map>
<create-other-layers>true</create-other-layers>
<keep-layer-names>false</keep-layer-names>
<merge>true</merge>
<lib-paths>
</lib-paths>
</mag>
</reader-options>
<writer-options>
<gds2>
<write-timestamps>true</write-timestamps>
<write-cell-properties>false</write-cell-properties>
<write-file-properties>false</write-file-properties>
<no-zero-length-paths>false</no-zero-length-paths>
<multi-xy-records>false</multi-xy-records>
<max-vertex-count>8000</max-vertex-count>
<max-cellname-length>32000</max-cellname-length>
<libname>LIB</libname>
</gds2>
<oasis>
<compression-level>2</compression-level>
<write-cblocks>false</write-cblocks>
<strict-mode>false</strict-mode>
<write-std-properties>1</write-std-properties>
<subst-char>*</subst-char>
<permissive>false</permissive>
</oasis>
<cif>
<polygon-mode>0</polygon-mode>
</cif>
<cif>
<dummy-calls>false</dummy-calls>
<blank-separator>false</blank-separator>
</cif>
<mag>
<lambda>0</lambda>
<tech/>
<write-timestamp>true</write-timestamp>
</mag>
</writer-options>
<connectivity>
<connection>DrainSource,contact,metal1</connection>
<connection>poly,contact,metal1</connection>
<connection>metal1,via1,metal2</connection>
<connection>metal2,via2,metal3</connection>
<connection>metal3,via3,metal4</connection>
<connection>metal4,via4,metal5</connection>
<connection>metal5,via5,metal6</connection>
<connection>metal6,via6,metal7</connection>
<connection>metal7,via7,metal8</connection>
<connection>metal8,via8,metal9</connection>
<connection>metal9,via9,metal10</connection>
<symbols>DrainSource='1/0 - 9/0'</symbols>
<symbols>poly='9/0'</symbols>
<symbols>contact='10/0'</symbols>
<symbols>metal1='11/0 + 11/1 + 11/2'</symbols>
<symbols>via1='12/0'</symbols>
<symbols>metal2='13/0 + 13/1 + 13/2'</symbols>
<symbols>via2='14/0'</symbols>
<symbols>metal3='15/0 + 15/1 + 15/2'</symbols>
<symbols>via3='16/0'</symbols>
<symbols>metal4='17/0 + 17/1 + 17/2'</symbols>
<symbols>via4='18/0'</symbols>
<symbols>metal5='19/0 + 19/1 + 19/2'</symbols>
<symbols>via5='20/0'</symbols>
<symbols>metal6='21/0 + 21/1 + 21/2'</symbols>
<symbols>via6='22/0'</symbols>
<symbols>metal7='23/0 + 23/1 + 23/2'</symbols>
<symbols>via7='24/0'</symbols>
<symbols>metal8='25/0 + 25/1 + 25/2'</symbols>
<symbols>via8='26/0'</symbols>
<symbols>metal9='27/0 + 27/1 + 27/2'</symbols>
<symbols>via9='28/0'</symbols>
<symbols>metal10='29/0 + 29/1 + 29/2'</symbols>
</connectivity>
</technology>

View File

@ -1,7 +0,0 @@
import os
CWD = os.environ.get("OPENRAM_TECH") + "/freepdk45/tf"
ui().importCds("default", CWD+"/display.drf", CWD+"/FreePDK45.tf", 1000, 1, CWD+"/layers.map")