begin port, copy drc lvs, simple init script and techmap file

This commit is contained in:
Rahul Bhagwat 2026-07-19 01:01:14 +05:30
parent ffb5074192
commit b1c607f299
No known key found for this signature in database
5 changed files with 1307 additions and 0 deletions

View File

@ -0,0 +1,41 @@
"""
This type of setup script should be placed in the setup_scripts directory in
the trunk
"""
import os
TECHNOLOGY = "ihp_sg13g2"
os.environ["MGC_TMPDIR"] = "/tmp"
###########################
# OpenRAM Paths
if "PDK_ROOT" in os.environ:
open_pdks = os.path.join(os.environ["PDK_ROOT"], "ihp-sg13g2", "libs.tech")
else:
raise SystemError("Unable to find the IHP SG13G2 PDK. Set PDK_ROOT.")
# The ngspice models work with Xyce too now
spice_model_dir = os.path.join(open_pdks, "ngspice", "models")
ihp_lib_ngspice = os.path.join(open_pdks, "ngspice", ".spiceinit")
if not os.path.exists(ihp_lib_ngspice):
raise SystemError("Did not find {} under {}".format(ihp_lib_ngspice, open_pdks))
os.environ["SPICE_MODEL_DIR"] = spice_model_dir
open_pdks = os.path.abspath(open_pdks)
ihp_magicrc = os.path.join(open_pdks, 'magic', "ihp-sg13g2.magicrc")
if not os.path.exists(ihp_magicrc):
raise SystemError("Did not find {} under {}".format(ihp_magicrc, open_pdks))
os.environ["OPENRAM_MAGICRC"] = ihp_magicrc
ihp_netgenrc = os.path.join(open_pdks, 'netgen', "ihp-sg13g2_setup.tcl")
if not os.path.exists(ihp_netgenrc):
raise SystemError("Did not find {} under {}".format(ihp_netgenrc, open_pdks))
os.environ["OPENRAM_NETGENRC"] = ihp_netgenrc
try:
DRCLVS_HOME = os.path.abspath(os.environ.get("DRCLVS_HOME"))
except:
DRCLVS_HOME= "not-found"
os.environ["DRCLVS_HOME"] = DRCLVS_HOME

View File

@ -0,0 +1,5 @@
"""
Import tech specific modules.
"""
from .tech import *

View File

@ -0,0 +1,528 @@
# frozen_string_literal: true
#=========================================================================================
# Copyright 2025 IHP PDK Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#=========================================================================================
#====================================================================================================================
#--------------------------------------------- IHP-SG13G2 DRC RULE DECK ---------------------------------------------
#====================================================================================================================
require 'time'
require 'logger'
require 'json'
require 'pathname'
require 'etc'
exec_start_time = Time.now
# Custom formatter including timestamp and memory usage
formatter = proc do |_severity, datetime, _progname, msg|
memory_usage = "#{RBA::Timer.memory_size / 1024}K"
"#{datetime}: Memory Usage (#{memory_usage}) : #{msg}\n"
end
# Create file logger
file_logger = Logger.new($log)
file_logger.formatter = formatter
# Create stdout logger
stdout_logger = Logger.new($stdout)
stdout_logger.formatter = formatter
# Don't buffer stdout
$stdout.sync = true
# MultiLogger class to broadcast to multiple loggers
class MultiLogger
def initialize(*targets)
@targets = targets
end
def info(msg)
@targets.each { |t| t.info(msg) }
end
def warn(msg)
@targets.each { |t| t.warn(msg) }
end
def error(msg)
@targets.each { |t| t.error(msg) }
end
end
# Use the multi-logger for your application
logger = MultiLogger.new(stdout_logger, file_logger)
#================================================
#----------------- FILE SETUP -------------------
#================================================
logger.info("Starting running IHP-SG13G2 KLayout DRC runset on #{$input}")
logger.info("Ruby Version for klayout: #{RUBY_VERSION}")
if $input
if $topcell
source($input, $topcell)
else
source($input)
end
end
logger.info('Loading database to memory is complete.')
if $report
logger.info("IHP-SG13G2 KLayout DRC runset output at: #{$report}")
report('Main DRC Run Report at', $report)
else
layout_dir = Pathname.new(RBA::CellView.active.filename).parent.realpath
report_path = layout_dir.join('sg13g2_drc_main.lyrdb').to_s
logger.info("IHP-SG13G2 KLayout DRC runset output at default location: #{report_path}")
report('Main DRC Run Report at', report_path)
end
#================================================
#------------------ SWITCHES --------------------
#================================================
logger.info('Evaluate switches.')
def bool_check?(obj)
obj.to_s.downcase == 'true'
end
TABLES = ($tables || 'main').split
logger.info("tables selected: #{TABLES.join(' ')}")
# Run mode setup
def setup_run_mode(run_mode)
case run_mode
when 'tiling'
tiles(500.um)
tile_borders(30.um)
when 'flat'
flat
else
deep
end
end
# Threads
$threads = ($threads || Etc.nprocessors).to_i
threads($threads)
logger.info("KLayout will use #{$threads} thread(s) in tiling mode.")
# Run mode
$run_mode = $run_mode || 'deep'
setup_run_mode($run_mode)
logger.info("#{$run_mode} mode is enabled.")
# FEOL
FEOL = !bool_check?($no_feol)
logger.info("FEOL enabled: #{FEOL}")
# BEOL
BEOL = !bool_check?($no_beol)
logger.info("BEOL enabled: #{BEOL}")
# OFFGRID
OFFGRID = !bool_check?($no_offgrid)
logger.info("OFFGRID enabled: #{OFFGRID}")
# ANGLE
ANGLE = !bool_check?($no_angle)
logger.info("ANGLE enabled: #{ANGLE}")
# PIN
PIN = !bool_check?($no_pin)
logger.info("PIN enabled: #{PIN}")
# FORBIDDEN
FORBIDDEN = !bool_check?($no_forbidden)
logger.info("FORBIDDEN enabled: #{FORBIDDEN}")
# RECOMMENDED
RECOMMENDED = !bool_check?($no_recommended)
logger.info("RECOMMENDED enabled: #{RECOMMENDED}")
# PreCheck DRC
PRECHECK_DRC = bool_check?($precheck_drc)
logger.info("PreCheck DRC enabled: #{PRECHECK_DRC}")
# Connectivity rules
# Needed for full FEOL runs and for explicit nwell/nbulay table runs.
conn_tables = %w[nwell nbulay]
CONNECTIVITY_RULES = FEOL || TABLES.any?{|x| conn_tables.include?(x)}
logger.info("CONNECTIVITY_RULES enabled: #{CONNECTIVITY_RULES}.")
#================================================
#------------- LAYERS DEFINITIONS ---------------
#================================================
# %include rule_decks/layers_def.drc
#================================================
# -------------------- UTILS --------------------
#================================================
# Method to get DRC values from JSON files
def get_drc_values(logger)
script_dir = File.expand_path(File.dirname(__FILE__))
$drc_json_default = $drc_json_default || File.expand_path(File.join(script_dir, 'rule_decks/sg13g2_tech_default.json'))
$drc_json = $drc_json || File.expand_path(File.join(script_dir, '../../python/sg13g2_pycell_lib/sg13g2_tech_mod.json'))
tech_rules = {}
if $drc_json && $drc_json != $drc_json_default
begin
tech_drc_content = File.read($drc_json)
tech_drc_data = JSON.parse(tech_drc_content)
tech_rules = tech_drc_data['drc_rules'] || {}
logger.info("Loaded TECH DRC rules values from #{$drc_json}")
rescue StandardError => e
logger.error("Error reading TECH DRC rules from #{$drc_json}: #{e.message}")
raise "Error reading TECH DRC rules from #{$drc_json}: #{e.message}"
end
end
begin
default_drc_content = File.read($drc_json_default)
default_drc_data = JSON.parse(default_drc_content)
default_rules = default_drc_data['drc_rules'] || {}
logger.info("Loaded default DRC rules values from #{$drc_json_default}")
rescue StandardError => e
logger.error("Error reading default DRC rules from #{$drc_json_default}: #{e.message}")
raise "Error reading default DRC rules from #{$drc_json_default}: #{e.message}"
end
merged_rules = tech_rules.merge(default_rules)
# Report any fallback rules used
missing_keys = default_rules.keys - tech_rules.keys
unless missing_keys.empty?
logger.warn('The following rules values were missing in tech json and default values were used:')
missing_keys.each { |rule| logger.warn(" - #{rule}") }
end
merged_rules
end
# Extract DRC rules values from the JSON file
drc_rules = get_drc_values(logger)
# Method to convert glob pattern to a case-insensitive glob-style pattern
def glob_to_case_insensitive_glob(glob)
wildcards = ['*', '?']
pattern = glob.chars.map do |c|
if c =~ /[A-Za-z]/
"[#{c.upcase}#{c.downcase}]"
elsif wildcards.include?(c)
c # Keep wildcard characters as they are
else
Regexp.escape(c)
end
end.join
pattern
end
#================================================
#-------------- COMMON DERIVATIONS --------------
#================================================
# === LAYOUT EXTENT ===
CHIP = extent.sized(0.0)
chip_area = CHIP.area
chip_bbox = CHIP.bbox
chip_w = chip_bbox.width
chip_l = chip_bbox.height
logger.info("Total area of the design is #{chip_area} um^2.")
logger.info("The design dimensions are #{chip_w} µm * #{chip_l} µm.")
# Enable tiling if chip area exceeds 100,000 µm²
en_tiles = chip_area > 1_000_000.um2
# === General Derivations ===
logger.info('Starting general IHP-SG13G2 derivations.')
# Pwell
logger.info('Starting Pwell derivations')
pwell_allowed = CHIP.not(pwell_block)
digisub_gap = digisub_drw.not(digisub_drw.sized(-1.nm))
pwell = pwell_allowed.not(nwell_drw).not(digisub_gap)
# nBulay
nbulay_tables = %w[main nbulay nwell activfiller schottkydiode]
if TABLES.any?{|x| nbulay_tables.include?(x)}
nbulay_gen_sized = nwell_drw.sized(-1.495.um).sized(0.495.um)
nbuLay_gen = nbulay_gen_sized.not(nbulay_block.join(res_drw))
nbuLay_gen_nbulay = nbuLay_gen.join(nbulay_drw)
end
# n activ
nact_pact_tables = %w[main pwellblock gatpoly cont latchup nwell nbulay]
if TABLES.any?{|x| nact_pact_tables.include?(x)}
logger.info('Starting n-Activ derivations')
nactiv = activ_drw.not(psd_drw.join(nsd_block))
end
# p activ
pact_pact_tables = %w[main pwellblock gatpoly cont npnsubstratetie latchup nwell nbulay schottkydiode contbar via1]
if TABLES.any?{|x| pact_pact_tables.include?(x)}
logger.info('Starting p-Activ derivations')
pactiv = activ_drw.and(psd_drw)
end
# p tap
ptap_tables = %w[main pwellblock npnsubstratetie latchup nwell nbulay schottkydiode contbar via1]
if TABLES.any?{|x| ptap_tables.include?(x)}
logger.info('Starting ptap derivations')
ptap = pactiv.and(pwell)
end
# n tap
ntap_tables = %w[main nwell nbulay]
if TABLES.any?{|x| ntap_tables.include?(x)}
logger.info('Starting ntap derivations')
ntap = nactiv.and(nwell_drw)
end
# activ FETs
pact_fet_tables = %w[main gatpoly nwell nbulay]
if TABLES.any?{|x| pact_fet_tables.include?(x)}
logger.info('Starting pact FET derivations')
pact_fet = pactiv.and(nwell_drw)
end
nact_fet_tables = %w[main pwellblock gatpoly latchup nwell nbulay cont]
if TABLES.any?{|x| nact_fet_tables.include?(x)}
logger.info('Starting nact FET derivations')
nact_fet = nactiv.and(pwell)
end
# Gate FETs
gate_tables = %w[main gatpoly nwell nbulay]
if TABLES.any?{|x| gate_tables.include?(x)}
logger.info('Starting Gate derivations')
res_mk = polyres_drw.join(res_drw)
ngate = nact_fet.and(gatpoly_drw)
pgate = pact_fet.and(gatpoly_drw)
end
# Cont derivations
cont_tables = %w[main cont contbar schottkydiode]
if TABLES.any?{|x| cont_tables.include?(x)}
logger.info('Starting cont/contbar general derivations')
cont_nseal = cont_drw.not(edgeseal_drw)
contbar = cont_nseal.non_squares
cont_sq = cont_nseal.not(contbar)
cont_nseal.forget
end
# Pad derivations
pad_tables = %w[main pad solderbump copperpillar]
if TABLES.any?{|x| pad_tables.include?(x)}
logger.info('Starting pad general derivations')
cu_pillarpad = passiv_pillar.and(dfpad_pillar).and(topmetal2_drw)
sbumppad = dfpad_sbump.and(passiv_sbump).and(topmetal2_drw)
pad = passiv_drw.and(dfpad_drw).and(topmetal2_drw)
end
# schottky_nbl derivations
schottky_tables = %w[main schottkydiode pwellblock]
if TABLES.any?{|x| schottky_tables.include?(x)}
logger.info('Starting schottky_nbl general derivations')
schottky_nbl_mk = recog_diode.and(thickgateox_drw).and(salblock_drw).and(nsd_block)
schottky_nbl_rec = nbulay_drw.not(nwell_drw).not_outside(schottky_nbl_mk)
schottky_nbl1 = ptap.holes.covering(schottky_nbl_rec)
end
# SVaricap derivations
svaricap_tables = %w[main cont nbulay nwell]
if TABLES.any?{|x| svaricap_tables.include?(x)}
logger.info('Starting SVaricap general derivations')
svaricap_gate = gatpoly_drw.and(nwell_drw).and(nbulay_drw)
svaricap_patt = glob_to_case_insensitive_glob("SVaricap")
svaricap_text = text_drw.texts(svaricap_patt)
svaricap_recog = activ_drw.not_outside(svaricap_gate).interacting(svaricap_text)
svaricap = nwell_drw.not_outside(svaricap_recog)
end
# HBT derivations
npn_tables = %w[main npnsubstratetie contbar via1]
if TABLES.any?{|x| npn_tables.include?(x)}
logger.info('Starting HBT general derivations')
sg13g2_patt = glob_to_case_insensitive_glob("npn13G2")
sg13g2l_patt = glob_to_case_insensitive_glob("npn13G2L")
sg13g2v_patt = glob_to_case_insensitive_glob("npn13G2V")
npn_mk = trans_drw.and(ptap.holes)
npn13g2 = npn_mk.interacting(text_drw.texts(sg13g2_patt)).covering(emwind_drw.and(activ_mask).and(nsd_block))
npn13g2l = npn_mk.interacting(text_drw.texts(sg13g2l_patt)).covering(emwind_drw.and(activ_drw))
npn13g2v = npn_mk.interacting(text_drw.texts(sg13g2v_patt)).covering(emwihv_drw.and(activ_drw))
end
# DRC tolerance value
drc_tole = 0.005.um
#================================================
#------------- LAYERS CONNECTIONS ---------------
#================================================
if CONNECTIVITY_RULES
logger.info('Starting IHP-SG13G2 DRC connectivity related derivations.')
# Wells
nwell_ring_niso = nwell_drw.with_holes.not(nbulay_drw)
nwell_holes = nwell_drw.holes.not(nwell_drw)
isoPWell = nbulay_drw.and(nwell_holes)
pwell_sub = pwell.not(isoPWell).join(nwell_ring_niso)
# conn layers
poly_con = gatpoly_drw.not(res_mk)
metal1_con = metal1_drw.not(metal1_res)
metal2_con = metal2_drw.not(metal2_res)
metal3_con = metal3_drw.not(metal3_res)
metal4_con = metal4_drw.not(metal4_res)
metal5_con = metal5_drw.not(metal5_res)
topmetal1_con = topmetal1_drw.not(topmetal1_res).not(ind_drw)
topmetal2_con = topmetal2_drw.not(topmetal2_res).not(ind_drw)
# Mimcap
mim_via = vmim_drw.join(topvia1_drw).and(mim_drw)
topvia1_n_cap = topvia1_drw.not(mim_via)
logger.info('Starting IHP-SG13G2 DRC connectivity setup')
# Inter-layer
connect(pwell_sub, pwell)
connect(pwell, ptap)
connect(nwell_drw, ntap)
connect(cont_drw, nactiv)
connect(cont_drw, pactiv)
connect(nbulay_drw, nbuLay_gen_nbulay)
connect(nbuLay_gen_nbulay, nwell_drw)
connect(ntap, cont_drw)
connect(ptap, cont_drw)
connect(poly_con, cont_drw)
connect(nact_fet, cont_drw)
connect(pact_fet, cont_drw)
connect(cont_drw, metal1_con)
connect(metal1_con, via1_drw)
connect(via1_drw, metal2_con)
connect(metal2_con, via2_drw)
connect(via2_drw, metal3_con)
connect(metal3_con, via3_drw)
connect(via3_drw, metal4_con)
connect(metal4_con, via4_drw)
connect(via4_drw, metal5_con)
connect(metal5_con, topvia1_n_cap)
connect(topvia1_n_cap, topmetal1_con)
connect(topmetal1_con, topvia2_drw)
connect(topvia2_drw, topmetal2_con)
# nwell nets
nwell_nets = nwell_drw.nets
end
#================================================
#------------ PRE-DEFINED FUNCTIONS -------------
#================================================
def get_circle(polygon_check)
maybe_circle1 = polygon_check.with_bbox_aspect_ratio(1)
maybe_circle2 = maybe_circle1.select { |polygon| polygon.num_points >= 16 }
# Circle detection based on area ratio of bounding box to shape area ≈ 4/π ≈ 1.2732
# (Derived from: (d^2) / (π * d^2 / 4) = 4/π)
maybe_circle2.without_holes.with_area_ratio(1.270, 1.276)
end
def get_octagon(polygon_check)
maybe_oct1 = polygon_check.with_bbox_aspect_ratio(1)
maybe_oct2 = maybe_oct1.select { |polygon| polygon.num_points == 8 }
h_edges_check = maybe_oct2.edges.with_angle(0, absolute)
maybe_oct3 = maybe_oct2.interacting(h_edges_check, 2, 2)
v_edges_check = maybe_oct3.edges.with_angle(90, absolute)
maybe_oct4 = maybe_oct3.interacting(v_edges_check, 2, 2)
diag_edges_check = maybe_oct4.edges.with_angle(44.5, 45.5, absolute)
maybe_oct4.interacting(diag_edges_check, 4, 4)
end
#================================================
#----------------- MAIN RUNSET ------------------
#================================================
logger.info('Starting IHP-SG13G2 DRC rules.')
# === FEOL ===
logger.info('Running all FEOL rules') if FEOL
# === BEOL ===
logger.info('Running all BEOL rules') if BEOL
# === PIN ===
logger.info('Running all PIN rules') if PIN
# === FORBIDDEN ===
logger.info('Running all FORBIDDEN rules') if FORBIDDEN
#================================================
#------------------- INCLUDES -------------------
#================================================
# %include rule_decks/feol/5_1_nwell.drc
# %include rule_decks/feol/5_2_pwellblock.drc
# %include rule_decks/feol/5_3_nbulay.drc
# %include rule_decks/feol/5_5_activ.drc
# %include rule_decks/feol/5_6_activfiller.drc
# %include rule_decks/feol/5_7_thickgateox.drc
# %include rule_decks/feol/5_8_gatpoly.drc
# %include rule_decks/feol/5_9_gatpolyfiller.drc
# %include rule_decks/feol/5_10_psd.drc
# %include rule_decks/feol/5_14_cont.drc
# %include rule_decks/feol/5_15_contbar.drc
# %include rule_decks/feol/6_1_npnsubstratetie.drc
# %include rule_decks/feol/6_7_schottkydiode.drc
# %include rule_decks/feol/7_2_latchup.drc
# %include rule_decks/beol/5_16_metal1.drc
# %include rule_decks/beol/5_17_metaln.drc
# %include rule_decks/beol/5_18_metalnfiller.drc
# %include rule_decks/beol/5_19_via1.drc
# %include rule_decks/beol/5_20_vian.drc
# %include rule_decks/beol/5_21_topvia1.drc
# %include rule_decks/beol/5_22_topmetal1.drc
# %include rule_decks/beol/5_23_topmetal1filler.drc
# %include rule_decks/beol/5_24_topvia2.drc
# %include rule_decks/beol/5_25_topmetal2.drc
# %include rule_decks/beol/5_26_topmetal2filler.drc
# %include rule_decks/beol/5_27_passiv.drc
# %include rule_decks/beol/6_9_copperpillar.drc
# %include rule_decks/beol/6_9_pad.drc
# %include rule_decks/beol/6_9_solderbump.drc
# %include rule_decks/beol/6_10_sealring.drc
# %include rule_decks/beol/6_11_mim.drc
# %include rule_decks/beol/7_3_metalslits.drc
# %include rule_decks/beol/9_1_lbe.drc
# %include rule_decks/pin/7_4_pin.drc
# %include rule_decks/geometry/3_1_offgrid.drc
# %include rule_decks/geometry/3_2_angle.drc
# %include rule_decks/forbidden/3_2_forbidden.drc
#================================================
#--------------------- TAIL ---------------------
#================================================
exec_end_time = Time.now
run_time = exec_end_time - exec_start_time
logger.info("KLayout DRC run for tables '#{TABLES.join(' ')}' completed in #{run_time.round(2)} seconds")

View File

@ -0,0 +1,513 @@
#==========================================================================
# Copyright 2024 IHP PDK Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# SPDX-License-Identifier: Apache-2.0
#==========================================================================
#===========================================================================================================
# ------------------------------------------ SG13G2 LVS RULE DECK ------------------------------------------
#===========================================================================================================
require 'time'
require 'logger'
require 'etc'
exec_start_time = Time.now
# Custom formatter including timestamp and memory usage
formatter = proc do |_severity, datetime, _progname, msg|
memory_usage = "#{RBA::Timer.memory_size / 1024}K"
"#{datetime}: Memory Usage (#{memory_usage}) : #{msg}\n"
end
# Create file logger
file_logger = Logger.new($log)
file_logger.formatter = formatter
# Create stdout logger
stdout_logger = Logger.new($stdout)
stdout_logger.formatter = formatter
# MultiLogger class to broadcast to multiple loggers
class MultiLogger
def initialize(*targets)
@targets = targets
end
def info(msg)
@targets.each { |t| t.info(msg) }
end
def warn(msg)
@targets.each { |t| t.warn(msg) }
end
def error(msg)
@targets.each { |t| t.error(msg) }
end
end
# Use the multi-logger for your application
logger = MultiLogger.new(stdout_logger, file_logger)
#================================================
#----------------- FILE SETUP -------------------
#================================================
logger.info("Starting running SG13G2 Klayout LVS runset on #{$input}")
logger.info("Ruby Version for klayout: #{RUBY_VERSION}")
if $input
if $topcell
src = source($input, $topcell)
else
src = source($input)
end
end
logger.info('Loading database to memory is complete.')
if $report
logger.info("SG13G2 Klayout LVS runset output at: #{$report}")
report_lvs($report)
else
layout_dir = Pathname.new(RBA::CellView.active.filename).parent.realpath
report_path = layout_dir.join("#{source.cell_name}.lvsdb").to_s
logger.info("SG13G2 Klayout LVS runset output at default location: #{source.cell_name}.lvsdb")
report_lvs($report_path)
end
#================================================
#---------------- SRAM SUPPORT ------------------
#================================================
# %include rule_decks/sram_integration.lvs
#================================================
#------------------ SWITCHES --------------------
#================================================
logger.info('Evaluate switches.')
def bool_check?(obj)
obj.to_s.downcase == 'true'
end
#=== NET NAMES OPTION ===
# true: use net names instead of numbers
# false: use numbers for nets
SPICE_WITH_NET_NAMES = !bool_check?($no_net_names)
logger.info("Extracted netlist with net names: #{SPICE_WITH_NET_NAMES}")
#=== COMMENTS OPTION ===
# true: put in comments with details
# false: no comments
SPICE_WITH_COMMENTS = bool_check?($spice_comments)
logger.info("Extracted netlist with comments in details: #{SPICE_WITH_COMMENTS}")
# NET_ONLY
NET_ONLY = bool_check?($net_only)
logger.info("Selected NET_ONLY option: #{NET_ONLY}")
# LAYOUT_NETLIST
LAYOUT_NETLIST_PATH = ($layout_netlist || '').to_s.strip
LAYOUT_NETLIST = !LAYOUT_NETLIST_PATH.empty?
logger.info("Selected LAYOUT_NETLIST option: #{LAYOUT_NETLIST ? LAYOUT_NETLIST_PATH : '(none)'}")
# IGNORE_TOP_PORTS_MISMATCH
floating_bulk_sram_top = src && floating_bulk_sram_cell?(src.cell_name)
IGNORE_TOP_PORTS_MISMATCH = bool_check?($ignore_top_ports_mismatch) || floating_bulk_sram_top
logger.info("Selected IGNORE_TOP_PORTS_MISMATCH option: #{IGNORE_TOP_PORTS_MISMATCH}")
if floating_bulk_sram_top && !bool_check?($ignore_top_ports_mismatch)
logger.info("Floating-bulk SRAM top cell '#{src.cell_name}' detected: enabling IGNORE_TOP_PORTS_MISMATCH.")
end
# IMPLICIT_NETS
IMPLICIT_NETS = ($implicit_nets || '').to_s.strip
logger.info("Selected IMPLICIT_NETS option: #{IMPLICIT_NETS.empty? ? '(none)' : IMPLICIT_NETS}")
# TOP_LVL_PINS
TOP_LVL_PINS = bool_check?($top_lvl_pins)
logger.info("Selected TOP_LVL_PINS option: #{TOP_LVL_PINS}")
# COMBINE
COMBINE_DEVICES = bool_check?($combine_devices)
logger.info("Selected COMBINE DEVICES option: #{COMBINE_DEVICES}")
# PURGE
PURGE = bool_check?($purge)
logger.info("Selected PURGE option: #{PURGE}")
# PURGE_NETS
PURGE_NETS = bool_check?($purge_nets)
logger.info("Selected PURGE_NETS option: #{PURGE_NETS}")
# SIMPLIFY
SIMPLIFY = !bool_check?($no_simplify)
logger.info("Selected SIMPLIFY option: #{SIMPLIFY}")
# SIMPLIFY
SERIES_RES = !bool_check?($no_series_res)
logger.info("Selected SERIES_RES option: #{SERIES_RES}")
# SIMPLIFY
PARALLEL_RES = !bool_check?($no_parallel_res)
logger.info("Selected PARALLEL_RES option: #{PARALLEL_RES}")
# === RUN MODE ===
case $run_mode
when 'deep'
#=== HIER MODE ===
deep
logger.info('deep mode is enabled.')
else
#=== FLAT MODE ===
flat
logger.info('flat mode is enabled.')
end
# === Tech Switches ===
#==================================================
# --------------- IMPLICIT CONNECTIONS -------------
#==================================================
# KLayout LVS docs:
# https://www.klayout.de/doc/manual/lvs.html
# "connect_implicit" creates a virtual combined physical net
# that is matched against the corresponding logical net name.
if !IMPLICIT_NETS.empty?
nets_to_connect = IMPLICIT_NETS.split(',').map(&:strip).reject(&:empty?)
if nets_to_connect.empty?
logger.info('WARNING : IMPLICIT_NETS was set but no valid entries were found after parsing.')
else
logger.info(
"Applying implicit connections (case-sensitive, pattern supported) for nets: #{nets_to_connect.join(', ')}"
)
nets_to_connect.each do |net_pattern|
connect_implicit(net_pattern)
end
end
end
#================================================
# --------------- CUSTOM CLASSES ----------------
#================================================
# %include rule_decks/custom_classes.lvs
# Instantiate a reader using the new delegate
reader = RBA::NetlistSpiceReader.new(CustomReader.new)
#=== GET NETLIST ===
unless NET_ONLY
if $schematic
schematic($schematic, reader)
logger.info("Netlist file: #{$schematic}")
else
exts = %w[spice cdl cir]
candidates = exts.map { |ext| "#{source.cell_name}.#{ext}" }
netlists = candidates.select { |f| File.exist?(f) }
if netlists.empty?
error("Netlist not found, tried: #{candidates}")
else
schematic(netlists[0], reader)
logger.info("Netlist file: #{netlists[0]}")
end
end
end
# Instantiate a writer using the new delegate
custom_spice_writer = RBA::NetlistSpiceWriter.new(CustomWriter.new)
custom_spice_writer.use_net_names = SPICE_WITH_NET_NAMES
custom_spice_writer.with_comments = SPICE_WITH_COMMENTS
if $target_netlist
logger.info("LVS extracted netlist at: #{$target_netlist}")
target_netlist($target_netlist, custom_spice_writer,
"Extracted by KLayout with SG13G2 LVS runset on : #{Time.now.strftime('%d/%m/%Y %H:%M')}")
else
layout_dir = Pathname.new(RBA::CellView.active.filename).parent.realpath
netlist_path = layout_dir.join("#{source.cell_name}_extracted.cir")
target_netlist(netlist_path.to_s, custom_spice_writer,
"Extracted by KLayout with SG13G2 LVS runset on : #{Time.now.strftime('%d/%m/%Y %H:%M')}")
logger.info("SG13G2 Klayout LVS extracted netlist file at: #{source.cell_name}_extracted.cir")
end
logger.info('Applying SRAM integration support.')
apply_sram_integration
#================================================================
#------------------------- MAIN RUNSET --------------------------
#================================================================
logger.info('Starting SG13G2 LVS runset')
if LAYOUT_NETLIST
logger.info('LAYOUT_NETLIST enabled: skipping derivations/connectivity/extraction and loading layout netlist from file.')
unless File.file?(LAYOUT_NETLIST_PATH)
error("LAYOUT_NETLIST file does not exist: #{LAYOUT_NETLIST_PATH}")
end
begin
netlist.read(LAYOUT_NETLIST_PATH, reader)
logger.info("Layout-side netlist file: #{LAYOUT_NETLIST_PATH}")
rescue StandardError => e
error("Failed to read LAYOUT_NETLIST '#{LAYOUT_NETLIST_PATH}': #{e.message}")
end
else
#================================================
#------------- LAYERS DEFINITIONS ---------------
#================================================
# %include rule_decks/layers_definitions.lvs
#================================================
#------------- LAYERS DERIVATIONS ---------------
#================================================
logger.info('Starting base layers derivations')
#==================================
# ------ GENERAL DERIVATIONS ------
#==================================
# %include rule_decks/general_derivations.lvs
#==================================
# ------ MOSFET DERIVATIONS -------
#==================================
# %include rule_decks/mos_derivations.lvs
#==================================
# ----- RF-MOSFET DERIVATIONS -----
#==================================
# %include rule_decks/rfmos_derivations.lvs
#================================
# ------ BJT DERIVATIONS --------
#================================
# %include rule_decks/bjt_derivations.lvs
#================================
# ----- DIODE DERIVATIONS -------
#================================
# %include rule_decks/diode_derivations.lvs
#================================
# ---- RESISTOR DERIVATIONS -----
#================================
# %include rule_decks/res_derivations.lvs
#==================================
# -------- CAP DERIVATIONS --------
#==================================
# %include rule_decks/cap_derivations.lvs
#================================
# ------ ESD DERIVATIONS --------
#================================
# %include rule_decks/esd_derivations.lvs
#=================================
# ----- Inductor DERIVATIONS -----
#=================================
# %include rule_decks/ind_derivations.lvs
#================================
# ------ Taps DERIVATIONS -------
#================================
# %include rule_decks/tap_derivations.lvs
#================================================
#------------ DEVICES CONNECTIVITY --------------
#================================================
# %include rule_decks/devices_connections.lvs
#================================================
#------------- DEVICES EXTRACTION ---------------
#================================================
logger.info('Starting SG13G2 LVS DEVICES EXTRACTION')
#================================
# ----- MOSFET EXTRACTION -------
#================================
cheat(SRAM_SHAREDSD_CELLS) {
# %include rule_decks/mos_extraction.lvs
}
#================================
# ---- RF-MOSFET EXTRACTION -----
#================================
# %include rule_decks/rfmos_extraction.lvs
#================================
# ------- BJT EXTRACTION --------
#================================
# %include rule_decks/bjt_extraction.lvs
#================================
# ------ DIODE EXTRACTION -------
#================================
# %include rule_decks/diode_extraction.lvs
#================================
# ---- RESISTOR EXTRACTIONS -----
#================================
# %include rule_decks/res_extraction.lvs
#==================================
# --------- CAP EXTRACTION --------
#==================================
# %include rule_decks/cap_extraction.lvs
#================================
# ------- ESD EXTRACTION --------
#================================
# %include rule_decks/esd_extraction.lvs
#=================================
# ----- Inductor EXTRACTIONS -----
#=================================
# %include rule_decks/ind_extraction.lvs
#================================
# ------- Taps EXTRACTIONS ------
#================================
# %include rule_decks/tap_extraction.lvs
end
# RF MOS model mapping
logger.info('Starting SG13G2 LVS RF MOS Model Mapping')
# %include rule_decks/rfmos_model_mapping.lvs
#==================================================
# ------------ COMPARISON PREPARATIONS ------------
#==================================================
logger.info('Starting SG13G2 LVS Options Preparations')
log_option_action = lambda do |label, option_name, enabled|
state = enabled ? 'ENABLED' : 'SKIPPED'
logger.info("[#{label}] #{option_name}: #{state}")
end
apply_netlist_options = lambda do |target_netlist, label|
log_option_action.call(label, 'simplify', SIMPLIFY)
target_netlist.simplify if SIMPLIFY
log_option_action.call(label, 'make_top_level_pins', TOP_LVL_PINS)
target_netlist.make_top_level_pins if TOP_LVL_PINS
log_option_action.call(label, 'combine_devices', COMBINE_DEVICES)
target_netlist.combine_devices if COMBINE_DEVICES
log_option_action.call(label, 'purge', PURGE)
target_netlist.purge if PURGE
log_option_action.call(label, 'purge_nets', PURGE_NETS)
target_netlist.purge_nets if PURGE_NETS
end
if NET_ONLY
logger.info('NET_ONLY enabled: apply extraction netlist options and skip comparison.')
if LAYOUT_NETLIST
logger.info('SG13G2 LVS flow: layout_netlist input -> option preparation -> write netlist -> exit.')
else
logger.info('SG13G2 LVS flow: extraction -> option preparation -> write netlist -> exit.')
end
logger.info('Starting SG13G2 LVS Simplification')
apply_netlist_options.call(netlist, 'layout_netlist')
netlist
else
if LAYOUT_NETLIST
logger.info('SG13G2 LVS flow: layout_netlist input -> alignment -> option preparation -> comparison.')
else
logger.info('SG13G2 LVS flow: extraction -> alignment -> option preparation -> comparison.')
end
# === Aligns the extracted netlist vs. the schematic ===
logger.info('Starting SG13G2 LVS Alignment')
align
#=== NETLIST OPTIONS ===
logger.info('Starting SG13G2 LVS Simplification')
apply_netlist_options.call(netlist, 'layout_netlist')
apply_netlist_options.call(schematic, 'schematic_netlist')
#=== IGNORE EXTREME VALUES ===
max_res(1e9)
min_caps(1e-18)
# === COMPARISON ===
if IGNORE_TOP_PORTS_MISMATCH
logger.info('Starting SG13G2 LVS Comparison')
success = compare
else
logger.info('Starting SG13G2 LVS Comparison in strict port mode.')
logger.info('flag_missing_ports enabled: missing/mislabeled top-level ports are treated as errors.')
success = compare && flag_missing_ports
end
#================================================
#------------- COMPARISON RESULTS ---------------
#================================================
if ! success
logger.info('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
logger.error("ERROR : Netlists don't match")
logger.info('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
else
logger.info('==========================================')
logger.info('INFO : Congratulations! Netlists match.')
logger.info('==========================================')
end
end
exec_end_time = Time.now
run_time = exec_end_time - exec_start_time
logger.info(format('LVS Total Run time %f seconds', run_time))

View File

@ -0,0 +1,220 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
"""IHP SG13G2 technology definition."""
import os
from openram import drc as d
tech_modules = d.module_type()
cell_properties = d.cell_properties()
cell_properties.ptx.model_is_subckt = True
###################################################
# GDS file info
###################################################
GDS = {}
GDS["unit"] = (0.001, 1e-6)
GDS["zoom"] = 0.5
###################################################
# Interconnect stacks
###################################################
poly_stack = ("poly", "contact", "m1")
active_stack = ("active", "contact", "m1")
m1_stack = ("m1", "via1", "m2")
m2_stack = ("m2", "via2", "m3")
m3_stack = ("m3", "via3", "m4")
m4_stack = ("m4", "via4", "m5")
m5_stack = ("m5", "topvia1", "topmetal1")
topmetal1_stack = ("topmetal1", "topvia2", "topmetal2")
lef_rom_interconnect = ["m1", "m2", "m3", "m4", "m5", "topmetal1", "topmetal2"]
layer_indices = {"poly": 0,
"active": 0,
"nwell": 0,
"pwell": 0,
"m1": 1,
"m2": 2,
"m3": 3,
"m4": 4,
"m5": 5,
"topmetal1": 6,
"topmetal2": 7}
feol_stacks = [poly_stack, active_stack]
beol_stacks = [m1_stack, m2_stack, m3_stack, m4_stack, m5_stack, topmetal1_stack]
layer_stacks = feol_stacks + beol_stacks
preferred_directions = {"poly": "V",
"active": "V",
"m1": "H",
"m2": "V",
"m3": "H",
"m4": "V",
"m5": "H",
"topmetal1": "V",
"topmetal2": "H"}
power_grid = topmetal1_stack
###################################################
# GDS Layer Map
###################################################
layer = {}
layer["activ"] = (1, 0)
layer["active"] = layer["activ"]
layer["gatpoly"] = (5, 0)
layer["poly"] = layer["gatpoly"]
layer["cont"] = (6, 0)
layer["contact"] = layer["cont"]
layer["metal1"] = (8, 0)
layer["m1"] = layer["metal1"]
layer["via1"] = (19, 0)
layer["metal2"] = (10, 0)
layer["m2"] = layer["metal2"]
layer["via2"] = (29, 0)
layer["metal3"] = (30, 0)
layer["m3"] = layer["metal3"]
layer["via3"] = (49, 0)
layer["metal4"] = (50, 0)
layer["m4"] = layer["metal4"]
layer["via4"] = (66, 0)
layer["metal5"] = (67, 0)
layer["m5"] = layer["metal5"]
layer["topvia1"] = (125, 0)
layer["topmetal1"] = (126, 0)
layer["topvia2"] = (133, 0)
layer["topmetal2"] = (134, 0)
layer["nwell"] = (31, 0)
layer["pwell"] = (46, 0)
layer["text"] = (63, 0)
layer["boundary"] = (189, 4)
layer["mem"] = (189, 4)
label_purpose = 25
layer_names = {}
for openram_name, pdk_name in [("active", "Activ"),
("poly", "GatPoly"),
("contact", "Cont"),
("m1", "Metal1"),
("via1", "Via1"),
("m2", "Metal2"),
("via2", "Via2"),
("m3", "Metal3"),
("via3", "Via3"),
("m4", "Metal4"),
("via4", "Via4"),
("m5", "Metal5"),
("topvia1", "TopVia1"),
("topmetal1", "TopMetal1"),
("topvia2", "TopVia2"),
("topmetal2", "TopMetal2"),
("nwell", "NWell"),
("pwell", "PWell"),
("text", "TEXT"),
("boundary", "prBoundary"),
("mem", "prBoundary")]:
layer_names[openram_name] = pdk_name
###################################################
# DRC/LVS rules setup
###################################################
parameter = {}
parameter["min_tx_size"] = 0.13
parameter["beta"] = 2
parameter["6T_inv_nmos_size"] = 0.26
parameter["6T_inv_pmos_size"] = 0.26
parameter["6T_access_size"] = 0.26
parameter["le_tau"] = 2.25
parameter["cap_relative_per_ff"] = 7.5
parameter["dff_clk_cin"] = 30.6
parameter["6tcell_wl_cin"] = 3
parameter["min_inv_para_delay"] = 2.4
parameter["sa_en_pmos_size"] = 0.72
parameter["sa_en_nmos_size"] = 0.27
parameter["sa_inv_pmos_size"] = 0.54
parameter["sa_inv_nmos_size"] = 0.27
parameter["bitcell_drain_cap"] = 0.1
drc = d.design_rules("ihp_sg13g2")
drc["grid"] = 0.005
drc["minwidth_tx"] = 0.13
drc["minlength_channel"] = 0.13
drc["pwell_to_nwell"] = 1.8
drc.add_layer("active", width=0.15, spacing=0.21)
drc.add_layer("poly", width=0.13, spacing=0.18)
drc.add_layer("contact", width=0.16, spacing=0.18)
drc.add_layer("m1", width=0.16, spacing=0.18)
drc.add_layer("m2", width=0.20, spacing=0.21)
drc.add_layer("m3", width=0.20, spacing=0.21)
drc.add_layer("m4", width=0.20, spacing=0.21)
drc.add_layer("m5", width=0.20, spacing=0.21)
drc.add_layer("topmetal1", width=1.64, spacing=1.64)
drc.add_layer("topmetal2", width=2.0, spacing=2.0)
drc["minwidth_contact"] = 0.16
drc["contact_to_contact"] = 0.18
drc["minwidth_via1"] = 0.19
drc["via1_to_via1"] = 0.22
drc["minwidth_via2"] = 0.19
drc["via2_to_via2"] = 0.22
drc["minwidth_via3"] = 0.19
drc["via3_to_via3"] = 0.22
drc["minwidth_via4"] = 0.19
drc["via4_to_via4"] = 0.22
###################################################
# Spice parameters
###################################################
spice = {}
spice["power"] = "VDD"
spice["ground"] = "VSS"
spice["nmos"] = "sg13_lv_nmos"
spice["pmos"] = "sg13_lv_pmos"
spice["fet_models"] = {"TT": "mos_tt", "SS": "mos_ss", "FF": "mos_ff"}
spice["fet_libraries"] = {
"TT": [[os.path.join(os.environ.get("SPICE_MODEL_DIR", ""), "cornerMOSlv.lib"), "mos_tt"]],
"SS": [[os.path.join(os.environ.get("SPICE_MODEL_DIR", ""), "cornerMOSlv.lib"), "mos_ss"]],
"FF": [[os.path.join(os.environ.get("SPICE_MODEL_DIR", ""), "cornerMOSlv.lib"), "mos_ff"]],
}
spice["supply_voltages"] = [1.08, 1.20, 1.32]
spice["temperatures"] = [125, 25, -55]
spice["nom_supply_voltage"] = 1.20
spice["nom_temperature"] = 25
spice["dff_in_cap"] = 0.001
spice["rise_time"] = 0.05
spice["fall_time"] = 0.05
spice["feasible_period"] = 10
spice["c_g_ideal"] = 0
spice["c_overlap"] = 0
spice["c_fringe"] = 0
spice["wire_unit_r"] = 0
spice["wire_unit_c"] = 0
spice["wire_r_per_um"] = 0
spice["wire_c_per_um"] = 0
spice["min_tx_gate_c"] = 0.1
spice["min_tx_drain_c"] = 0.1
spice["dff_out_cap"] = 0.1
spice["nom_threshold"] = 0.4
spice["default_event_frequency"] = 100
spice["inv_leakage"] = 0
spice["nand2_leakage"] = 0
spice["nand3_leakage"] = 0
spice["nand4_leakage"] = 0
spice["nor2_leakage"] = 0
spice["dff_leakage"] = 0
spice["bitcell_leakage"] = 0
spice["sa_transconductance"] = 1
spice["dff_setup"] = 0
spice["dff_hold"] = 0
drc_name = "klayout"
lvs_name = "klayout"
pex_name = "magic"
layer_properties = d.layer_properties()