diff --git a/technology/ihp_sg13g2/__init__.py b/technology/ihp_sg13g2/__init__.py new file mode 100644 index 000000000..22b2a68b1 --- /dev/null +++ b/technology/ihp_sg13g2/__init__.py @@ -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 diff --git a/technology/ihp_sg13g2/tech/__init__.py b/technology/ihp_sg13g2/tech/__init__.py new file mode 100644 index 000000000..a89d6c9e0 --- /dev/null +++ b/technology/ihp_sg13g2/tech/__init__.py @@ -0,0 +1,5 @@ +""" +Import tech specific modules. +""" + +from .tech import * diff --git a/technology/ihp_sg13g2/tech/ihp_sg13g2.lydrc b/technology/ihp_sg13g2/tech/ihp_sg13g2.lydrc new file mode 100644 index 000000000..cba91a445 --- /dev/null +++ b/technology/ihp_sg13g2/tech/ihp_sg13g2.lydrc @@ -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") diff --git a/technology/ihp_sg13g2/tech/ihp_sg13g2.lylvs b/technology/ihp_sg13g2/tech/ihp_sg13g2.lylvs new file mode 100644 index 000000000..e99d0fed4 --- /dev/null +++ b/technology/ihp_sg13g2/tech/ihp_sg13g2.lylvs @@ -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)) diff --git a/technology/ihp_sg13g2/tech/tech.py b/technology/ihp_sg13g2/tech/tech.py new file mode 100644 index 000000000..4d1393490 --- /dev/null +++ b/technology/ihp_sg13g2/tech/tech.py @@ -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()