mirror of https://github.com/VLSIDA/OpenRAM.git
Merge branch 'dev' into cacti_model
This commit is contained in:
commit
39ae1270d7
|
|
@ -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/
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -302,4 +302,3 @@ class local_bitcell_array(bitcell_base_array.bitcell_base_array):
|
|||
Clears the bit exclusions
|
||||
"""
|
||||
self.bitcell_array.clear_exclude_bits()
|
||||
|
||||
|
|
|
|||
|
|
@ -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 """
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 "
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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 & 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 & 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>
|
||||
|
|
@ -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 & nwell
|
||||
pactive = active_in_nwell & pplus
|
||||
ntie = active_in_nwell & nplus
|
||||
pgate = pactive & poly
|
||||
psd = pactive - pgate
|
||||
lv_pgate = pgate - vtg - thkox
|
||||
gv_pgate = pgate & vtg - vth - thkox
|
||||
hv_pgate = pgate - vtg - vth & thkox
|
||||
|
||||
active_in_pwell = active & pwell
|
||||
nactive = active_in_pwell & nplus
|
||||
ptie = active_in_pwell & pplus
|
||||
ngate = nactive & poly
|
||||
nsd = nactive - ngate
|
||||
lv_ngate = ngate - vtg - thkox
|
||||
gv_ngate = ngate & vtg - vth - thkox
|
||||
hv_ngate = ngate - vtg - vth & 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" => psd, "G" => lv_pgate, "tS" => psd, "tD" => psd, "tG" => poly, "W" => nwell })
|
||||
extract_devices(mos4("PMOS_VTG"), { "SD" => psd, "G" => gv_pgate, "tS" => psd, "tD" => psd, "tG" => poly, "W" => nwell })
|
||||
extract_devices(mos4("PMOS_VTH"), { "SD" => psd, "G" => hv_pgate, "tS" => psd, "tD" => psd, "tG" => poly, "W" => nwell })
|
||||
|
||||
# NMOS transistor device extraction
|
||||
extract_devices(mos4("NMOS_VTL"), { "SD" => nsd, "G" => lv_ngate, "tS" => nsd, "tD" => nsd, "tG" => poly, "W" => pwell })
|
||||
extract_devices(mos4("NMOS_VTG"), { "SD" => nsd, "G" => gv_ngate, "tS" => nsd, "tD" => nsd, "tG" => poly, "W" => pwell })
|
||||
extract_devices(mos4("NMOS_VTH"), { "SD" => nsd, "G" => hv_ngate, "tS" => nsd, "tD" => nsd, "tG" => poly, "W" => 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 => absolute_tolerance] [, :relative => relative_tolerance])
|
||||
tolerance("PMOS_LVT", "W", :absolute => 1.nm, :relative => 0.001)
|
||||
tolerance("PMOS_LVT", "L", :absolute => 1.nm, :relative => 0.001)
|
||||
tolerance("PMOS_GVT", "W", :absolute => 1.nm, :relative => 0.001)
|
||||
tolerance("PMOS_GVT", "L", :absolute => 1.nm, :relative => 0.001)
|
||||
tolerance("PMOS_HVT", "W", :absolute => 1.nm, :relative => 0.001)
|
||||
tolerance("PMOS_HVT", "L", :absolute => 1.nm, :relative => 0.001)
|
||||
tolerance("NMOS_LVT", "W", :absolute => 1.nm, :relative => 0.001)
|
||||
tolerance("NMOS_LVT", "L", :absolute => 1.nm, :relative => 0.001)
|
||||
tolerance("NMOS_GVT", "W", :absolute => 1.nm, :relative => 0.001)
|
||||
tolerance("NMOS_GVT", "L", :absolute => 1.nm, :relative => 0.001)
|
||||
tolerance("NMOS_HVT", "W", :absolute => 1.nm, :relative => 0.001)
|
||||
tolerance("NMOS_HVT", "L", :absolute => 1.nm, :relative => 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>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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>
|
||||
|
|
@ -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")
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue