Files
tt-support-tools/precheck/tech-files/sky130A_mr.drc
T
Sylvain Munaut c4972e27ca fix(precheck/sky130A): Fix MR_licon.SP6 again
Previous fix caused issue with resistors since that check must not
apply to anything in prec_res zone according to licon.9

Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
2026-05-05 22:22:48 +02:00

1774 lines
59 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DRC for SKY130 according to :
# https://skywater-pdk.readthedocs.io/en/latest/rules/periphery.html
# https://skywater-pdk.readthedocs.io/en/latest/rules/layers.html
#
# Distributed under GNU GPLv3: https://www.gnu.org/licenses/
#
# History :
# 2022-6-22 : 2022.6.30_01.07 release
#
# 2023-6-14 : 2023.6.14_01.08 release
#
# 2024-2-11 : 2024.2.11_01.09 release
#
# 2024-5-26 : Update rectMCON_peri to use outside instead of not
# 2024-8-15 :
# * Avoid assigning to constant variables
# * Fix defaults for arguments when they are not passed in the CLI
#
# 2024-8-18 :
# * Add new rules:
# * `MR_lvtn.OVL.2`
# * `MR_tunm.CON.1`
# * `rdl.1`
# * `rdl.2`
# * `MR_thkox.CON.1`
# * `MR_rdl.CON.1`
# * `MR_li.WID.4`
# * `moduleCut.1`
# * Modify `licon` checks to account for ("non-bar" | exempt prec_resistor)
# * Rename `licon` checks
# 2024-9-1 :
# * Change the default of sram_exclude to false
# 2025-10-29 : 2025.10.29_01.10 release
# * Updated to be in line with the current Skywater required rules
##########################################################################################
release = "2025.10.29_01.10"
require "time"
require "logger"
exec_start_time = Time.now
logger = Logger.new(STDOUT)
logger.formatter =
proc do |severity, datetime, progname, msg|
"#{msg}
"
end
# optionnal for a batch launch : klayout -b -rd input=my_layout.gds -rd report=sky130_drc.txt -r drc_sky130.drc
source($input, $top_cell) if $input
if $report
report("SKY130 DRC runset", $report)
else
report(
"SKY130 DRC runset",
File.join(File.dirname(RBA::CellView.active.filename), "sky130_drc.txt")
)
end
if not defined? $feol
$feol = "false"
end
if not defined? $beol
$beol = "false"
end
if not defined? $offgrid
$offgrid = "false"
end
if not defined? $floating_met
$floating_met = "false"
end
if not defined? $sram_exclude
$sram_exclude = "false"
end
AL = true # do not change
CU = false # do not change
# choose betwen only one of AL or CU back-end flow here :
backend_flow = AL
# enable / disable rule groups
if $feol == "0" || $feol == "false"
FEOL = false
else
FEOL = true # front-end-of-line checks
end
if $beol == "0" || $beol == "false"
BEOL = false
else
BEOL = true # back-end-of-line checks
end
if $offgrid == "0" || $offgrid == "false"
OFFGRID = false
else
OFFGRID = true # manufacturing grid/angle checks
end
if $seal == "0" || $seal == "false"
SEAL = false
else
SEAL = true # SEAL RING checks
end
if $floating_met == "0" || $floating_met == "false"
FLOATING_MET = false
else
FLOATING_MET = true # back-end-of-line checks
end
if $sram_exclude == "0" || $sram_exclude == "false"
SRAM_EXCLUDE = false # back-end-of-line checks
else
SRAM_EXCLUDE = true
end
if $info == "0" || $info == "false"
INFO = false
else
INFO = true # front-end-of-line checks
end
logger.info("Args:")
logger.info("\tsram_exclude: #{$sram_exclude}")
logger.info("\tfeol: #{$feol}")
logger.info("\tbeol: #{$beol}")
logger.info("\tfloating_met: #{$floating_met}")
logger.info("\toffgrid: #{$offgrid}")
logger.info("\tseal: #{$seal}")
# klayout setup
########################
# use a tile size of 1mm - not used in deep mode-
# tiles(1000.um)
# use a tile border of 10 micron:
# tile_borders(1.um)
#no_borders
# hierachical
deep
$thr ? threads($thr) : threads(4)
# if more inof is needed, set true
# verbose(true)
verbose(true)
# layers definitions
########################
# all except purpose (datatype) 5 -- label and 44 -- via
li_wildcard = "67/20"
li_res_wildcard = "67/13"
mcon_wildcard = "67/44"
m1_wildcard = "68/20"
via_wildcard = "68/44"
m2_wildcard = "69/20"
via2_wildcard = "69/44"
m3_wildcard = "70/20"
via3_wildcard = "70/44"
m4_wildcard = "71/20"
via4_wildcard = "71/44"
m5_wildcard = "72/20"
nsdm_wildcard = "93/44"
psdm_wildcard = "94/20"
nwell_wildcard = "64/20"
diff = input(65, 20)
tap = polygons(65, 44)
nwell = polygons(nwell_wildcard)
dnwell = polygons(64, 18)
pwbm = polygons(19, 44)
pwde = polygons(124, 20)
natfet = polygons(124, 21)
hvtr = polygons(18, 20)
hvtp = polygons(78, 44)
ldntm = polygons(11, 44)
hvi = polygons(75, 20)
tunm = polygons(80, 20)
lvtn = polygons(125, 44)
poly = polygons(66, 20)
hvntm = polygons(125, 20)
nsdm = polygons(nsdm_wildcard)
psdm = polygons(psdm_wildcard)
rpm = polygons(86, 20)
urpm = polygons(79, 20)
npc = polygons(95, 20)
licon = polygons(66, 44)
li = polygons(li_wildcard)
li_res = polygons(li_res_wildcard)
mcon = polygons(mcon_wildcard)
m1 = polygons(m1_wildcard)
via = polygons(via_wildcard)
m2 = polygons(m2_wildcard)
via2 = polygons(via2_wildcard)
m3 = polygons(m3_wildcard)
via3 = polygons(via3_wildcard)
m4 = polygons(m4_wildcard)
via4 = polygons(via4_wildcard)
m5 = polygons(m5_wildcard)
pad = polygons(76, 20)
nsm = polygons(61, 20)
capm = polygons(89, 44)
cap2m = polygons(97, 44)
rdl = polygons(74, 20)
vhvi = polygons(74, 21)
uhvi = polygons(74, 22)
npn = polygons(82, 20)
inductor = polygons(82, 24)
vpp = polygons(82, 64)
pnp = polygons(82, 44)
lvs_prune = polygons(84, 44)
ncm = polygons(92, 44)
padcenter = polygons(81, 20)
mf = polygons(76, 44)
areaid_sl = polygons(81, 1)
areaid_ce = polygons(81, 2)
areaid_fe = polygons(81, 3)
areaid_sc = polygons(81, 4)
areaid_sf = polygons(81, 6)
areaid_sw = polygons(81, 7)
areaid_sr = polygons(81, 8)
areaid_mt = polygons(81, 10)
areaid_dt = polygons(81, 11)
areaid_ft = polygons(81, 12)
areaid_ww = polygons(81, 13)
areaid_ld = polygons(81, 14)
areaid_ns = polygons(81, 15)
areaid_ij = polygons(81, 17)
areaid_zr = polygons(81, 18)
areaid_ed = polygons(81, 19)
areaid_de = polygons(81, 23)
areaid_rd = polygons(81, 24)
areaid_dn = polygons(81, 50)
areaid_cr = polygons(81, 51)
areaid_cd = polygons(81, 52)
areaid_st = polygons(81, 53)
areaid_op = polygons(81, 54)
areaid_en = polygons(81, 57)
areaid_en20 = polygons(81, 58)
areaid_le = polygons(81, 60)
areaid_hl = polygons(81, 63)
areaid_sd = polygons(81, 70)
areaid_po = polygons(81, 81)
areaid_it = polygons(81, 84)
areaid_et = polygons(81, 101)
areaid_lvt = polygons(81, 108)
areaid_re = polygons(81, 125)
areaid_ag = polygons(81, 79)
poly_rs = polygons(66, 13)
poly_fill = polygons(66, 99)
diff_rs = polygons(65, 13)
pwell_rs = polygons(64, 13)
li_rs = polygons(67, 13)
cfom = polygons(22, 20)
thkox = polygons(75, 21)
areaid_ce_merged = areaid_ce.merged
# Define a new custom function that selects polygons by their number of holes:
# It will return a new layer containing those polygons with min to max holes.
# max can be nil to omit the upper limit.
class DRC::DRCLayer
def with_holes(min, max)
new_data = RBA::Region.new
self.data.each do |p|
new_data.insert(p) if p.holes >= (min || 0) && (!max || p.holes <= max)
end
DRC::DRCLayer.new(@engine, new_data)
end
end
# DRC section
########################
log("DRC section")
if FEOL
log("FEOL section")
# dnwell
log("START: 64/18 (dnwell)")
dnwell
.width(3.0, euclidian)
.output(
"MRW_dnwell.WID.1",
"MRW_dnwell.WID.1: min. dnwell width : 3.0um"
)
dnwell
.not(uhvi.or(vhvi))
.space(6.3, euclidian)
.output(
"MRW_dnwell.SP.1",
"MRW_dnwell.SP.1 : min. dnwell spacing : 6.3um"
)
log("END: 64/18 (dnwell)")
not_sram = layout(source.cell_obj).select("-*sky130_sram_*kbyte_*")
not_sram_nsdm = not_sram.input(nsdm_wildcard)
not_sram_psdm = not_sram.input(psdm_wildcard)
not_sram_nwell = not_sram.input(nwell_wildcard)
# This is a hack, should be reverted
not_io =
layout(source.cell_obj).select(
"-*sky130_fd_io__gpiov2_amux",
"-*sky130_fd_io__simple_pad_and_busses"
)
not_io_nwell = not_io.input(nwell_wildcard)
# nwell
log("START: 64/20 (nwell)")
nwell.width(0.84, euclidian).output(
"MR_nwell.WID.1",
"MR_nwell.WID.1 : min. nwell width : 0.84um"
)
nwell.space(1.27, euclidian).output(
"MR_nwell.SP.1",
"MR_nwell.SP.1 : min. nwell spacing (merged if less) : 1.27um"
)
nwell_interact = not_sram_nwell.and(not_io_nwell).merge
dnwell.enclosing(nwell_interact.holes, 1.03, euclidian).output(
"MR_nwell.ENC.1",
"MR_nwell.ENC.1 : min enclosure of nwellHole by dnwell : 1.03um"
)
hvmarker = hvi.or(rdl).or(uhvi).or(vhvi)
nwell
.interacting(nwell.and(hvmarker))
.not(hvmarker)
.output("MR_nwell.CON.1", "MR_nwell.CON.1 : HVnwell must be enclosed by hv marker")
log("END: 64/20 (nwell)")
# hvtp
log("START: 78/44 (hvtp)")
hvtp.width(0.38, euclidian).output(
"MR_hvtp.WID.1",
"MR_hvtp.WID.1 : min. hvtp width : 0.38um"
)
hvtp.space(0.38, euclidian).output(
"MR_hvtp.SP.1",
"MR_hvtp.SP.1 : min. hvtp spacing : 0.38um"
)
log("END: 78/44 (hvtp)")
# pwde
log("START: 124/20 (pwde)")
pwde.width(0.84, euclidian).output(
"pwde.1",
"pwde.1 : min. pwde width : 0.84um"
)
pwde
.and(uhvi.or(vhvi))
.space(1.27, euclidian)
.output("MR_pwde.SP.1", "MR_pwde.SP.1 : min. pwde inside v20 spacing : 1.27um")
log("END: 124/20 (pwde)")
# hvtr
log("START: 18/20 (htvr)")
hvtr.width(0.38, euclidian).output(
"hvtr.1",
"hvtr.1 : min. hvtr width : 0.38um"
)
hvtr.separation(hvtp, 0.38, euclidian).output(
"hvtr.2",
"hvtr.2 : min. hvtr spacing : 0.38um"
)
hvtr.and(hvtp).output("MR_hvtr.CON.1", "MR_hvtr.CON.1 : hvtr must not overlap hvtp")
log("END: 18/20 (htvr)")
# lvtn
log("START: 25/44 (lvtn)")
lvtn.width(0.38, euclidian).output(
"lvtn.1a",
"lvtn.1a : min. lvtn width : 0.38um"
)
lvtn.space(0.38, euclidian).output(
"lvtn.2",
"lvtn.2 : min. lvtn spacing : 0.38um"
)
lvtn
.interacting(nwell)
.not_inside(nwell)
.edges
.and(nwell)
.output("MR_lvtn.OVL.2", "MR_lvtn.OVL.2 : lvtn must not straddle nwell")
log("END: 25/44 (lvtn)")
# ncm
log("START: 92/44 (ncm)")
ncm.width(0.38, euclidian).output("MR_ncm.WID.1", "MR_ncm.WID.1 : min. ncm width : 0.38um")
ncm.space(0.38, euclidian).output(
"ncm.2a",
"ncm.2a : min. ncm spacing : 0.38um"
)
log("END: 92/44 (ncm)")
# diff-tap
log("START: 65/20 (diff)")
difftap = diff.or(tap)
diff_width = diff.rectangles.width(0.15, euclidian).polygons
diff_cross_areaid_ce =
diff_width
.edges
.outside_part(areaid_ce)
.not(diff_width.outside(areaid_ce).edges)
diff_cross_areaid_ce.output(
"difftap.1",
"difftap.1 : min. diff width across areaid:ce : 0.15um"
)
diff
.outside(areaid_ce)
.width(0.15, euclidian)
.output(
"difftap.1_a",
"difftap.1_a : min. diff width in periphery : 0.15um"
)
diff
.inside(areaid_ce)
.width(0.14, euclidian)
.output(
"difftap.2",
"difftap.2 : min. diff width inside areadid:ce : 0.14um"
)
log("END: 65/20 (diff)")
log("START: 65/44 (tap)")
tap_width = tap.rectangles.width(0.15, euclidian).polygons
tap_cross_areaid_ce =
tap_width
.edges
.outside_part(areaid_ce)
.not(tap_width.outside(areaid_ce).edges)
tap_cross_areaid_ce.output(
"difftap.1_b",
"difftap.1_b : min. tap width across areaid:ce : 0.15um"
)
tap
.not(areaid_ce)
.width(0.15, euclidian)
.output("MR_diff.WID.2", "MR_diff.WID.2 : min. tap width in periphery : 0.15um")
log("END: 65/44 (tap)")
difftap.space(0.27, euclidian).output(
"difftap.3",
"difftap.3 : min. difftap spacing : 0.27um"
)
# tunm
log("START: 80/20 (tunm)")
tunm.width(0.41, euclidian).output(
"tunm.1",
"tunm.1 : min. tunm width : 0.41um"
)
tunm.space(0.5, euclidian).output(
"tunm.2",
"tunm.2 : min. tunm spacing : 0.5um"
)
tunm.output(
"MR_tunm.CON.1",
"MR_tunm.CON.1 : use of layer tunm is prohibited"
)
log("END: 80/20 (tunm)")
# thkox
log("START: 80/20 (thkox)")
thkox_peri = thkox.not(areaid_ce)
thkox_peri.width(0.6, euclidian).output(
"thkox.1",
"thkox.1 : min. thkox width inside periphery : 0.6um"
)
thkox_peri.space(0.7, euclidian).output(
"thkox.2",
"thkox.2 : min. thkox spacing inside periphery : 0.7um"
)
# thkox
hvi
.interacting(diff)
.not_inside(diff)
.edges
.and(diff)
.output("MR_thkox.CON.1", "MR_thkox.CON.1 : thkox must not straddle diff")
# MR_hv.SP.10: ndiff inside (v5, v12, or v20 and thkox) minimum spacing when non-abutting ptap
# Note: Using available area identifiers - areaid_ft (81,12) for v12, areaid_ed (81,19) for v20
# Assuming v5 might be areaid_ft or another high-voltage area
ptap_hv = tap.and(psdm).and(hvi) # ptap inside hv areas and thkox
ndiff_hv = diff.and(nsdm).and(hvi).not_interacting(ptap_hv) # ndiff inside hv areas and thkox
# Check spacing when ndiff is non-abutting ptap
gap_violations = ndiff_hv.separation(ndiff_hv, 1.07, euclidian).polygons
gap_violations.interacting(ptap_hv).output(
"MR_hv.SP.10",
"MR_hv.SP.10 : ndiff inside (v12, v20 and thkox) minimum spacing when non-abutting ptap : 1.07um"
)
log("END: 80/20 (thkox)")
# poly
log("START: 66/20 (poly)")
poly.width(0.15, euclidian).output(
"MR_poly.WID.2",
"MR_poly.WID.2 : min. poly width : 0.15um"
)
poly
.not(areaid_ce)
.space(0.21, euclidian)
.output("MR_poly.SP.1", "MR_poly.SP.1 : min. poly spacing : 0.21um")
core_poly_gap = poly.inside(areaid_ce).drc(width(projection) <= 0.15).polygons
poly
.interacting(core_poly_gap)
.isolated(0.16, projection)
.output("MR_poly.SP.4", "MR_poly.SP.4 : min. poly spacing inside areaid:core : 0.16um")
# Additional MR poly resistor rules
# MR_poly.LEN.2: poly:res minimum length for rpoly_hp and rpoly_hp2K resistors ≥ 0.66
poly_rs.drc(bbox_max < 0.66).output(
"MR_poly.LEN.2",
"MR_poly.LEN.2 : poly:res minimum length for rpoly_hp and rpoly_hp2K resistors : 0.66um"
)
# MR_poly.WID.3: poly:res minimum width ≥ 0.33
poly_rs.width(0.33).output(
"MR_poly.WID.3",
"MR_poly.WID.3 : poly:res minimum width : 0.33um"
)
log("END: 66/20 (poly)")
# ldntm
log("START: 80/20 (ldntm)")
ldntm_core = ldntm.and(areaid_ce)
ldntm_core.width(0.7, euclidian).output(
"ldntm.1",
"ldntm.1 : min. ldntm width inside areaid:core : 0.7um"
)
ldntm_core.space(0.7, euclidian).output(
"ldntm.2",
"ldntm.2 : min. ldntm spacing inside areaid:core : 0.7um"
)
log("END: 80/20 (ldntm)")
# rpm
log("START: 86/20 (rpm)")
rpm.width(1.27, euclidian).output(
"MR_rpm.WID.1",
"MR_rpm.WID.1 : min. rpm width : 1.27um"
)
rpm.space(0.84, euclidian).output(
"MR_rpm.SP.1",
"MR_rpm.SP.1 : min. rpm spacing : 0.84um"
)
log("END: 86/20 (rpm)")
# rdl
log("START: 74/20 (rdl)")
rdl.width(10.um, euclidian).output("MR_rdl.WID.1", "MR_rdl.WID.1 : min. rdl width : 10um")
rdl.space(10.um, euclidian).output("MR_rdl.SP.1", "MR_rdl.SP.1 : min. rdl spacing : 10um")
rdl.output("MR_rdl.CON.1", "MR_rdl.CON.1 : use of rdl layer is prohibited")
log("END: 74/20 (rdl)")
# urpm
log("START: 79/20 (urpm)")
urpm.width(1.27, euclidian).output(
"MR_urpm.WID.1",
"MR_urpm.WID.1 : min. rpm width : 1.27um"
)
urpm.space(0.84, euclidian).output(
"MR_urpm.SP.1",
"MR_urpm.SP.1 : min. rpm spacing : 0.84um"
)
log("END: 79/20 (urpm)")
# npc
log("START: 95/20 (npc)")
npc.width(0.27, euclidian).output("MR_npc.WID.1", "MR_npc.WID.1 : min. npc width : 0.27um")
npc.space(0.27, euclidian).output(
"npc.2",
"npc.2 : min. npc spacing, should be manually merged if less than : 0.27um"
)
log("END: 95/20 (npc)")
# nsdm
log("START: 93/44 (nsdm)")
if SRAM_EXCLUDE
not_sram_nsdm
.not(areaid_ce)
.space(0.38, euclidian)
.output("MR_nsdm.SP.1", "MR_nsdm.SP.1 : min. nsdm spacing in periphery : 0.38um")
not_sram_nsdm
.outside(areaid_ce)
.width(0.38, euclidian)
.output("MR_nsdm.WID.2", "MR_nsdm.WID.2 : min. nsdm width in periphery : 0.38um")
not_sram_nsdm_width =
not_sram_nsdm
.width(0.38, euclidian)
.polygons
.interacting(areaid_ce_merged)
not_sram_nsdm_width.interacting(
not_sram_nsdm_width.edges.outside_part(areaid_ce_merged)
).output("MR_nsdm.WID.1", "MR_nsdm.WID.1 : min. diff width across areaid:ce : 0.38um")
nsdm_space =
not_sram_nsdm
.space(0.38, euclidian)
.polygons
.interacting(areaid_ce_merged)
nsdm_space.interacting(
nsdm_space.edges.outside_part(areaid_ce_merged)
).output("MR_nsdm.SP.2", "MR_nsdm.SP.2 : min. spacing across areaid:ce : 0.38um")
not_sram_nsdm
.inside(areaid_ce)
.width(0.29, euclidian)
.output("MR_nsdm.WID.3", "MR_nsdm.WID.3 : min. nsdm width in areaid:ce : 0.29um")
not_sram_nsdm
.inside(areaid_ce)
.space(0.29, euclidian)
.output("MR_nsdm.SP.3", "MR_nsdm.SP.3 : min. nsdm spacing in areaid:ce : 0.29um")
not_sram_nsdm
.inside(areaid_ce)
.space(0.38, projection)
.with_internal_angle(0)
.output(
"nsdm.6a",
"nsdm.6a : min. nsdm spacing on parallel edges in areaid:ce : 0.38um"
)
else
nsdm
.not(areaid_ce)
.space(0.38, euclidian)
.output("MR_nsdm.SP.1", "MR_nsdm.SP.1 : min. nsdm spacing in periphery : 0.38um")
nsdm
.outside(areaid_ce)
.width(0.38, euclidian)
.output("MR_nsdm.WID.2", "MR_nsdm.WID.2 : min. nsdm width in periphery : 0.38um")
nsdm_width =
nsdm.width(0.38, euclidian).polygons.interacting(areaid_ce_merged)
nsdm_width.interacting(
nsdm_width.edges.outside_part(areaid_ce_merged)
).output("MR_nsdm.WID.1", "MR_nsdm.WID.1 : min. diff width across areaid:ce : 0.38um")
nsdm_space =
nsdm.space(0.38, euclidian).polygons.interacting(areaid_ce_merged)
nsdm_space.interacting(
nsdm_space.edges.outside_part(areaid_ce_merged)
).output("MR_nsdm.SP.2", "MR_nsdm.SP.2 : min. spacing across areaid:ce : 0.38um")
nsdm
.inside(areaid_ce)
.width(0.29, euclidian)
.output("MR_nsdm.WID.3", "MR_nsdm.WID.3 : min. nsdm width in areaid:ce : 0.29um")
nsdm
.inside(areaid_ce)
.space(0.29, euclidian)
.output("MR_nsdm.SP.3", "MR_nsdm.SP.3 : min. nsdm spacing in areaid:ce : 0.29um")
nsdm
.inside(areaid_ce)
.space(0.38, projection)
.with_internal_angle(0)
.output(
"nsdm.6a",
"nsdm.6a : min. nsdm spacing on parallel edges in areaid:ce : 0.38um"
)
end # SRAM_EXCLUDE
log("END: 93/44 (nsdm)")
# psdm
log("START: 94/20 (psdm)")
if SRAM_EXCLUDE
not_sram_psdm
.not(areaid_ce)
.space(0.38, euclidian)
.output("MR_psdm.SP.1", "MR_psdm.SP.1 : min. psdm spacing in periphery : 0.38um")
not_sram_psdm
.outside(areaid_ce)
.width(0.38, euclidian)
.output("MR_psdm.WID.2", "MR_psdm.WID.2 : min. psdm width in periphery : 0.38um")
not_sram_psdm_width =
not_sram_psdm
.width(0.38, euclidian)
.polygons
.interacting(areaid_ce_merged)
not_sram_psdm_width.interacting(
not_sram_psdm_width.edges.outside_part(areaid_ce_merged)
).output("MR_psdm.WID.1", "MR_psdm.WID.1 : min. diff width across areaid:ce : 0.38um")
psdm_space =
not_sram_psdm
.space(0.38, euclidian)
.polygons
.interacting(areaid_ce_merged)
psdm_space.interacting(
psdm_space.edges.outside_part(areaid_ce_merged)
).output("MR_psdm.SP.2", "MR_psdm.SP.2 : min. spacing across areaid:ce : 0.38um")
not_sram_psdm
.inside(areaid_ce)
.width(0.29, euclidian)
.output("MR_psdm.WID.3", "MR_psdm.WID.3 : min. psdm width in areaid:ce : 0.29um")
not_sram_psdm
.inside(areaid_ce)
.space(0.29, euclidian)
.output("MR_psdm.SP.3", "MR_psdm.SP.3 : min. psdm spacing in areaid:ce : 0.29um")
not_sram_psdm
.inside(areaid_ce)
.space(0.38, projection)
.with_internal_angle(0)
.output(
"psdm.6a",
"psdm.6a : min. psdm spacing on parallel edges in areaid:ce : 0.38um"
)
not_sram_psdm
.and(not_sram_nsdm)
.and(diff)
.output(
"nsdm_psdm_overlap",
"nsdm_psdm_overlap : nsdm overlaps psdm over active"
)
else
psdm
.not(areaid_ce)
.space(0.38, euclidian)
.output("MR_psdm.SP.1", "MR_psdm.SP.1 : min. psdm spacing in periphery : 0.38um")
psdm
.outside(areaid_ce)
.width(0.38, euclidian)
.output("MR_psdm.WID.2", "MR_psdm.WID.2 : min. psdm width in periphery : 0.38um")
psdm_width =
psdm.width(0.38, euclidian).polygons.interacting(areaid_ce_merged)
psdm_width.interacting(
psdm_width.edges.outside_part(areaid_ce_merged)
).output("MR_psdm.WID.1", "MR_psdm.WID.1 : min. diff width across areaid:ce : 0.38um")
psdm_space =
psdm.space(0.38, euclidian).polygons.interacting(areaid_ce_merged)
psdm_space.interacting(
psdm_space.edges.outside_part(areaid_ce_merged)
).output("MR_psdm.SP.2", "MR_psdm.SP.2 : min. spacing across areaid:ce : 0.38um")
psdm
.inside(areaid_ce)
.width(0.29, euclidian)
.output("MR_psdm.WID.3", "MR_psdm.WID.3 : min. psdm width in areaid:ce : 0.29um")
psdm
.inside(areaid_ce)
.space(0.29, euclidian)
.output("MR_psdm.SP.3", "MR_psdm.SP.3 : min. psdm spacing in areaid:ce : 0.29um")
psdm
.inside(areaid_ce)
.space(0.38, projection)
.with_internal_angle(0)
.output(
"psdm.6a",
"psdm.6a : min. psdm spacing on parallel edges in areaid:ce : 0.38um"
)
psdm
.and(nsdm)
.and(diff)
.output(
"nsdm_psdm_overlap",
"nsdm_psdm_overlap : nsdm overlaps psdm over active"
)
end # SRAM_EXCLUDE
log("END: 94/20 (psdm)")
# licon
log("START: 66/44 (licon)")
polyi = poly_fill.and(poly)
poly_licon = (polyi.and(licon)).and(areaid_ce)
licon_peri = licon.outside(areaid_ce)
licon_squares = licon.drc((squares) & (bbox_min == 0.17).polygons)
licon_not_squares = licon.not(licon_squares)
licon_bars = licon.drc((bbox_max == 2.0) & (bbox_min == 0.19).polygons)
#if INFO
# licon_bars.output(
# "licon_bar",
# "licon bar : Info only"
# )
#end
if SEAL
ringLICON = licon.drc(with_holes > 0)
rectLICON = licon.not(ringLICON)
else
rectLICON = licon
end
rectLICON.non_rectangles.output(
"licon.1_c",
"licon.1_c : licon should be rectangle"
)
prec_resistor = (rpm | urpm) & psdm & (poly.interacting(poly_rs))
licon_peri.not(prec_resistor).space(0.17, euclidian).output(
"MR_licon.SP.1",
"MR_licon.SP.1: min. licon spacing in periphery : 0.17um"
)
licon_not_prec_resistor = licon.not(prec_resistor)
licon_not_prec_resistor.drc(length != 0.17).output(
"licon.1",
"licon.1: min/max. licon length : 0.17um"
)
licon_peri
.and(difftap)
.separation(npc, 0.09, euclidian)
.output(
"licon.13",
"licon.13: min. licon on diff spacing to npc in periphery : 0.09um"
)
licon_peri
.and(difftap)
.and(npc)
.output(
"licon.13_a",
"licon.13_a: licon on diff in periphery can't overlap npc"
)
licon
.and(poly)
.and(difftap)
.output(
"licon.17",
"licon.17 : Licons may not overlap both poly and (diff or tap)"
)
xfom = difftap.not(poly)
licon1ToXfom = licon.interacting(licon.and(xfom))
polyLicon1 =
(licon.not(licon1ToXfom)).interacting((licon.not(licon1ToXfom)).and(poly))
polyLicon1_CORE = polyLicon1.and(areaid_ce)
npc.enclosing(polyLicon1_CORE, 0.045, euclidian).output(
"licon.6",
"licon.6 : npc min enclosure of poly_licon inside areaid:core : 0.045um"
)
polyLicon1_CORE.not(npc).output(
"licon.6",
"licon.6 : npc min enclosure of poly_licon inside areaid:core : 0.045um"
)
# Additional MR licon bar rules
# MR_licon.ANG.1: licon outside areaid:seal must be orthogonal
licon_outside_seal = licon.outside(areaid_sl)
licon_outside_seal.with_angle(0..90).output(
"MR_licon.ANG.1",
"MR_licon.ANG.1 : licon outside areaid:seal must be orthogonal"
)
# MR_licon.CON.13: licon bar outside prec_resistor is prohibited
licon_bars_outside_prec_resistor = licon_bars.outside(prec_resistor)
licon_bars_outside_prec_resistor.output(
"MR_licon.CON.13",
"MR_licon.CON.13 : licon bar outside prec_resistor is prohibited"
)
licon_squares_rpm_urpm = licon_squares.interacting(rpm | urpm)
licon_squares_rpm_urpm.output(
"MR_licon.CON.xx",
"MR_licon.CON.xx : licon square interacting with rpm/urpm is prohibited"
)
# MR_licon.SP.2: licon bar end to end minimum spacing ≥ 0.35
licon_bars.drc(width == 2.00).space(0.35, euclidian).output(
"MR_licon.SP.2",
"MR_licon.SP.2 : licon bar end to end minimum spacing : 0.35um"
)
# MR_licon.SP.3: licon bar side to side minimum spacing ≥ 0.51
# This is handled by the general spacing rule above, but we can add specific check
licon_bars.drc(width == 0.19).space(0.51, euclidian).output(
"MR_licon.SP.3",
"MR_licon.SP.3 : licon bar side to side minimum spacing : 0.51um"
)
# MR_licon.SP.4: licon bar minimum space to licon square ≥ 0.51
licon_bars.separation(licon_squares, 0.51, euclidian).output(
"MR_licon.SP.4",
"MR_licon.SP.4 : licon bar minimum space to licon square : 0.51um"
)
# MRW_licon.SP.16: 0.19 width bar licons (post-maskgen) bar licon spacing < 0.51
#licon_bars.space(0.51, euclidian).output(
# "MRW_licon.SP.16",
# "MRW_licon.SP.16 : 0.19 width bar licons (post-maskgen) bar licon spacing < 0.51"
#)
# MR_licon.SP.5: tap_licon minimum space to tap edge abutting diff ≥ 0.06
tap_licon = licon.and(tap)
tap_licon.separation(diff, 0.06, euclidian).output(
"MR_licon.SP.5",
"MR_licon.SP.5 : tap_licon minimum space to tap edge abutting diff : 0.06um"
)
# MR_licon.SP.6: poly_licon minimum space to psdm (overlap prohibited) ≥ 0.11
poly_licon_not_prec_resistor = licon_not_prec_resistor.and(poly)
poly_licon_not_prec_resistor.separation(psdm, 0.11, euclidian).output(
"MR_licon.SP.6",
"MR_licon.SP.6 : poly_licon minimum space to psdm (overlap prohibited) : 0.11um"
)
poly_licon_not_prec_resistor.interacting(psdm).output(
"MR_licon.SP.6_overlap",
"MR_licon.SP.6 : licon and psdm must not overlap"
)
# MR_licon.SP.8: licon on diff minimum space to gate outside areaid:standardc (overlap prohibited) ≥ 0.055
licon_on_diff = licon.and(difftap)
licon_on_diff_outside_stdc = licon_on_diff.outside(areaid_sc)
licon_on_diff_outside_stdc.separation(poly, 0.055, euclidian).output(
"MR_licon.SP.8",
"MR_licon.SP.8 : licon on diff minimum space to gate outside areaid:standardc (overlap prohibited) : 0.055um"
)
# MR_licon.WID.1: licon (non-bar) minimum/maximum width == 0.17
licon_non_bar = licon.drc((length <= 0.17).polygons)
licon_non_bar.drc(width != 0.17).output(
"MR_licon.WID.1",
"MR_licon.WID.1 : licon (non-bar) minimum/maximum width : 0.17um"
)
# MR_licon.WID.2: licon bar exact size: 0.19 µm × 2.0 µm
licon_bars.drc(width(euclidian) != 0.19).output(
"MR_licon.WID.2",
"MR_licon.WID.2 : licon bar exact width : 0.19um"
)
licon_bars.drc(bbox_max != 2.0).output(
"MR_licon.WID.2",
"MR_licon.WID.2 : licon bar exact length : 2.0um"
)
log("END: 66/44 (licon)")
#nsm
log("START: 61/20 (nsm)")
nsm.space(4.0, euclidian).output("MR_nsm.SP.1", "MR_nsm.SP.1 : nsm min. spacing : 4.0um")
nsm.width(3.0, euclidian).output("MR_nsm.WID.1", "MR_nsm.WID.1 : nsm min. width : 3.0um")
log("END: 61/20 (nsm)")
# CAPM
log("START: 89/44 (capm)")
capm.width(1.0, euclidian).output(
"MR_capm.WID.1",
"MR_capm.WID.1 : min. capm width : 1.0um"
)
capm.space(0.84, euclidian).output(
"MR_capm.SP.1",
"MR_capm.SP.1 : min. capm spacing : 0.84um"
)
m3_bot_plate = ((capm.and(m3)).sized(0.14))
m3_bot_plate
.isolated(1.2, euclidian)
.polygons
.not(m3)
.output("MR_capm.SP.2", "MR_capm.SP.2 : min. spacing between met3_bot_plate : 1.2um")
(m3.not_interacting(m3_bot_plate)).separation(
m3_bot_plate,
1.2,
euclidian
).output(
"MR_capm.SP.2",
"MR_capm.SP.2 : min. spacing between met3_bot_plate and met3 : 1.2um"
)
# capm.and(m3).enclosing(m3, 0.14, euclidian).output("capm.3", "capm.3 : min. capm and m3 enclosure of m3 : 0.14um")
m3.enclosing(capm, 0.14, euclidian).output(
"MR_capm.ENC.1",
"MR_capm.ENC.1 : min. m3 enclosure of capm : 0.14um"
)
capm.enclosing(via3, 0.14, euclidian).output(
"MR_capm.ENC.2",
"MR_capm.ENC.2 : min. capm enclosure of via3 : 0.14um"
)
capm.separation(via3, 0.14, euclidian).output(
"MR_capm.SP.3",
"MR_capm.SP.3 : min. capm spacing to via3 : 0.14um"
)
(m3.not_interacting(capm)).separation(capm, 0.5, euclidian).output(
"MR_capm.SP.2",
"MR_capm.SP.2 : Min spacing of capm and met3 not overlapping capm : 0.5um"
)
log("END: 89/44 (capm)")
# CAP2M
log("START: 97/44 (cap2m)")
cap2m.width(1.0, euclidian).output(
"MR_cap2m.WID.1",
"MR_cap2m.WID.1 : min. cap2m width : 1.0um"
)
cap2m.space(0.84, euclidian).output(
"MR_cap2m.SP.1",
"MR_cap2m.SP.1 : min. cap2m spacing : 0.84um"
)
m4
.interacting(cap2m)
.isolated(1.2, euclidian)
.output("MR_cap2m.SP.2", "MR_cap2m.SP.2 : min. cap2m spacing : 1.2um")
(m4.interacting(cap2m)).isolated(1.2, euclidian).output(
"MR_cap2m.SP.2",
"MR_cap2m.SP.2 : min. spacing of m4_bot_plate : 1.2um"
)
cap2m
.and(m4)
.enclosing(m4, 0.14, euclidian)
.output("MR_cap2m.ENC.1", "MR_cap2m.ENC.1 : min. m4 enclosure of cap2m : 0.14um")
m4.enclosing(cap2m, 0.14, euclidian).output(
"MR_cap2m.ENC.1",
"MR_cap2m.ENC.1 : min. m4 enclosure of cap2m : 0.14um"
)
cap2m.enclosing(via4, 0.2, euclidian).output(
"MR_cap2m.ENC.2",
"MR_cap2m.ENC.2 : min. cap2m enclosure of via4 : 0.2um"
)
cap2m.separation(via4, 0.2, euclidian).output(
"MR_cap2m.SP.3",
"MR_cap2m.SP.3 : min. cap2m spacing to via4 : 0.2um"
)
(m4.not_interacting(cap2m)).separation(cap2m, 0.5, euclidian).output(
"MR_cap2m.SP.2",
"MR_cap2m.SP.2 : Min spacing of cap2m and met4 not overlapping cap2m : 0.5um"
)
log("END: 97/44 (cap2m)")
end #FEOL
if BEOL
log("BEOL section")
# li
log("START: 67/20 (li)")
li_outside_or_touching_areaidce =
(li.outside(areaid_ce)).or(
li.interacting(areaid_ce).not(li.inside(areaid_ce))
)
li_outside_or_touching_areaidce.width(0.17, euclidian).output(
"li.1",
"li.1 : min. li width outside or crossing areaid:ce : 0.17um"
)
li_outside_or_touching_areaidce.space(0.17, euclidian).output(
"li.3",
"li.3 : min. li spacing outside or crossing areaid:ce : 0.17um"
)
licon_peri = licon.not(areaid_ce)
li_edges_with_less_enclosure =
li.enclosing(licon_peri, 0.08, projection).second_edges
error_corners = li_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu)
li_interact = licon_peri.interacting(error_corners.polygons(1.dbu))
li_interact.output(
"li.5",
"li.5 : min. li enclosure of licon of 2 adjacent edges : 0.08um"
)
linotace = li.not(li.interacting(areaid_ce))
linotace.with_area(nil, 0.0561).output(
"li.6",
"li.6 : min. li area : 0.0561um²"
)
li_core = li.and(areaid_ce)
li_core.space(0.14, euclidian).output(
"li.7",
"li.7 : min. li core spacing : 0.14um"
)
li_core.width(0.14, euclidian).output(
"li.8",
"li.8 : min. li core width : 0.14um"
)
log("END: 67/20 (li)")
# li res
log("START: 67/13 (li_res)")
li_res.width(0.29, euclidian).output(
"MR_li.WID.4",
"MR_li.WID.4 : li:res minimum width : 0.29um"
)
log("END: 67/13 (li_res)")
# ct
log("START: 67/44 (mcon)")
mconnotace = mcon.not(areaid_ce)
if SEAL
ringMCON = mcon.drc(with_holes > 0)
rectMCON = mcon.not(ringMCON)
else
rectMCON = mcon
end
rectMCON_peri = rectMCON.outside(areaid_ce)
rectMCON.non_rectangles.output(
"MR_mcon.ANG.1",
"MR_mcon.ANG.1: mcon must be rectangular"
)
# rectMCON_peri.edges.without_length(0.17).output("ct.1_a/b", "ct.1_a/b : minimum/maximum width of mcon : 0.17um")
rectMCON_peri.drc(width(euclidian) < 0.17).output(
"MR_mcon.WID.1",
"MR_mcon.WID.1 : minimum width of mcon : 0.17um"
)
rectMCON_peri.drc(length > 0.17).output(
"MR_mcon.LEN.1",
"MR_mcon.LEN.1 : maximum length of mcon : 0.17um"
)
mcon.space(0.19, euclidian).output(
"MR_mcon.SP.1",
"MR_mcon.SP.1 : min. mcon spacing : 0.19um"
)
if SEAL
ringMCON.width(0.17, euclidian).output(
"MR_mcon.WID.2",
"MR_mcon.WID.2 : min. width of ring-shaped mcon : 0.17um"
)
ringMCON.drc(width(euclidian) >= 0.175).output(
"MR_mcon.WID.3",
"MR_mcon.WID.3 : max. width of ring-shaped mcon : 0.175um"
)
ringMCON.not(areaid_sl).output(
"MR_mcon.CON.1",
"MR_mcon.CON.1: ring-shaped mcon must be enclosed by areaid_sl"
)
end
mconnotace.not(li).output("MR_mcon.CON.10", "MR_mcon.CON.10 : mcon should covered by li")
log("END: 67/44 (mcon)")
# m1
log("START: 68/20 (m1)")
m1.width(0.14, euclidian).output("MR_met1.WID.1", "MR_met1.WID.1 : min. m1 width : 0.14um")
huge_m1 = m1.sized(-1.5).sized(1.5).snap(0.005) & m1
non_huge_m1 = m1.edges - huge_m1
huge_m1 = huge_m1.edges.outside_part(m1.merged)
non_huge_m1.space(0.14, euclidian).output(
"MR_met1.SP.1",
"MR_met1.SP.1 : min. m1 spacing : 0.14um"
)
(
huge_m1.separation(non_huge_m1, 0.28, euclidian) +
huge_m1.space(0.28, euclidian)
).output("MRW_met1.SP.2", "MRW_met1.SP.2 : min. 3um.m1 spacing m1 : 0.28um")
#not_in_cell6 = layout(source.cell_obj).select("-s8cell_ee_plus_sseln_a", "-s8cell_ee_plus_sseln_b", "-s8cell_ee_plus_sselp_a", "-s8cell_ee_plus_sselp_b", "-s8fpls_pl8", "-s8fs_cmux4_fm")
not_in_cell6 =
layout(source.cell_obj).select(
"-s8cell_ee_plus_sseln_a",
"-s8cell_ee_plus_sseln_b",
"-s8cell_ee_plus_sselp_a",
"-s8cell_ee_plus_sselp_b",
"-s8fs_cmux4_fm"
)
not_in_cell6_m1 = not_in_cell6.input(m1_wildcard)
not_in_cell6_m1.enclosing(mconnotace, 0.03, euclidian).output(
"MR_met1.ENC.1",
"MR_met1.ENC.1 : min. m1 enclosure of mcon : 0.03um"
)
mconnotace.not(m1).output(
"MR_met1.CON.1",
"MR_met1.CON.1 : mcon periphery must be enclosed by m1"
)
in_cell6 =
layout(source.cell_obj).select(
"-*",
"+s8cell_ee_plus_sseln_a",
"+s8cell_ee_plus_sseln_b",
"+s8cell_ee_plus_sselp_a",
"+s8cell_ee_plus_sselp_b",
"+s8fpls_pl8",
"+s8fs_cmux4_fm"
)
in_cell6_m1 = in_cell6.input(m1_wildcard)
in_cell6_m1.enclosing(mcon, 0.005, euclidian).output(
"m1.4a",
"m1.4a : min. m1 enclosure of mcon for specific cells : 0.005um"
)
in_cell6_m1.not(m1).output(
"m1.4a_a",
"m1.4a_a : mcon periph must be enclosed by met1 for specific cells"
)
m1.with_area(0..0.083).output("MR_met1.AR.1", "MR_met1.AR.1 : min. m1 area : 0.083um²")
m1
.holes
.with_area(0..0.14)
.output("MR_met1.AR.2", "MR_met1.AR.2 : min. m1 with holes area : 0.14um²")
if FLOATING_MET
m1.not_interacting(via.or(mcon)).output(
"m1.x",
"floating met1, must interact with via1"
)
end
if backend_flow == AL
#Could flag false positive, fix would be to add .rectangles for m1
mconnotace_edges_with_less_enclosure_m1 =
m1.enclosing(mconnotace, 0.06, projection).second_edges
error_corners_m1 =
mconnotace_edges_with_less_enclosure_m1.width(angle_limit(100.0), 1.dbu)
mconnotace_interact_m1 =
mconnotace.interacting(error_corners_m1.polygons(1.dbu))
mconnotace_interact_m1.output(
"m1.5",
"m1.5 : min. m1 enclosure of mcon of 2 adjacent edges : 0.06um"
)
end
log("END: 68/20 (m1)")
# via
log("START: 68/44 (via)")
if backend_flow == AL
if SEAL
ringVIA = via.drc(with_holes > 0)
rectVIA = via.not(ringVIA)
else
rectVIA = via
end
via_not_mt = rectVIA.not(areaid_mt)
via_not_mt.non_rectangles.output(
"via.1a",
"via.1a : via outside of moduleCut should be rectangular"
)
via_not_mt.width(0.15, euclidian).output(
"via.1a_a",
"via.1a_a : min. width of via outside of moduleCut : 0.15um"
)
# via_not_mt.edges.without_length(nil, 0.15 + 1.dbu).output("via.1a_b", "via.1a_b : maximum length of via : 0.15um")
via_not_mt.drc(length > 0.15).output(
"via.1a_b",
"via.1a_b : maximum length of via : 0.15um"
)
via.space(0.17, euclidian).output(
"via.2",
"via.2 : min. via spacing : 0.17um"
)
if SEAL
ringVIA.width(0.2, euclidian).output(
"via.3",
"via.3 : min. width of ring-shaped via : 0.2um"
)
ringVIA.drc(width(euclidian) >= 0.205).output(
"via.3_a",
"via.3_a : max. width of ring-shaped via : 0.205um"
)
ringVIA.not(areaid_sl).output(
"via.3_b",
"via.3_b: ring-shaped via must be enclosed by areaid_sl"
)
end
m1
.edges
.enclosing(rectVIA.drc(width == 0.15), 0.055, euclidian)
.output("MR_via1.ENC.1", "MR_via1.ENC.1 : min. m1 enclosure of 0.15um via : 0.055um")
rectVIA
.squares
.drc(width == 0.15)
.not(m1)
.output("MR_via1.CON.1", "MR_via1.CON.1 : 0.15um via must be enclosed by met1")
via1_edges_with_less_enclosure_m1 =
m1
.edges
.enclosing(rectVIA.drc(width == 0.15), 0.085, projection)
.second_edges
error_corners_via1 =
via1_edges_with_less_enclosure_m1.width(angle_limit(100.0), 1.dbu)
via2_interact = via.interacting(error_corners_via1.polygons(1.dbu))
via2_interact.output(
"via.5a",
"via.5a : min. m1 enclosure of 0.15um via of 2 adjacent edges : 0.085um"
)
end
log("END: 68/44 (via)")
# m2
log("START: 69/20 (m2)")
via_inside_periphery = via.and(areaid_ce)
m2.width(0.14, euclidian).output("MR_met2.WID.1", "MR_met2.WID.1 : min. m2 width : 0.14um")
huge_m2 = m2.sized(-1.5).sized(1.5).snap(0.005) & m2
non_huge_m2 = m2.edges - huge_m2
huge_m2 = huge_m2.edges.outside_part(m2.merged)
via_outside_periphery = via.not(areaid_ce)
non_huge_m2.space(0.14, euclidian).output(
"m2.2",
"m2.2 : min. m2 spacing : 0.14um"
)
(
huge_m2.separation(non_huge_m2, 0.28, euclidian) +
huge_m2.space(0.28, euclidian)
).output("MRW_met2.SP.2", "MRW_met2.SP.2 : min. 3um.m2 spacing m2 : 0.28um")
m2.with_area(0..0.0676).output("MR_met2.AR.1", "MR_met2.AR.1 : min. m2 area : 0.0676um²")
m2
.holes
.with_area(0..0.14)
.output("MR_met2.AR.2", "MR_met2.AR.2 : min. m2 holes area : 0.14um²")
if FLOATING_MET
m2.not_interacting(via.or(via2)).output(
"m2.x",
"floating met2, must interact with via1 or via2"
)
end
if backend_flow == AL
m2.enclosing(via_outside_periphery, 0.055, euclidian).output(
"m2.4",
"m2.4 : min. m2 enclosure of via : 0.055um"
)
via_outside_periphery.not(m2).output(
"m2.4_a",
"m2.4_a : via in periphery must be enclosed by met2"
)
m2.enclosing(via_inside_periphery, 0.045, euclidian).output(
"m2.4_b",
"m2.4_B : min. m2 enclosure of via inside areaid:core : 0.045um"
)
via_edges_with_less_enclosure_m2 =
m2.enclosing(via_outside_periphery, 0.085, projection).second_edges
error_corners =
via_edges_with_less_enclosure_m2.width(angle_limit(100.0), 1.dbu)
via_interact = via.interacting(error_corners.polygons(1.dbu))
via_interact.output(
"m2.5",
"m2.5 : min. m2 enclosure of via of 2 adjacent edges : 0.085um"
)
end
log("END: 69/20 (m2)")
# via2
log("START: 69/44 (via2)")
if backend_flow == AL
if SEAL
ringVIA2 = via2.drc(with_holes > 0)
rectVIA2 = via2.not(ringVIA2)
else
rectVIA2 = via2
end
via2_not_mt = rectVIA2.not(areaid_mt)
via2_not_mt.non_rectangles.output(
"via2.1a",
"via2.1a : via2 outside of moduleCut should be rectangular"
)
via2_not_mt.width(0.2, euclidian).output(
"via2.1a_a",
"via2.1a_a : min. width of via2 outside of moduleCut : 0.2um"
)
via2_not_mt
.edges
.without_length(nil, 0.2 + 1.dbu)
.output("MR_via2.LEN.1", "MR_via2.LEN.1 : maximum length of via2 : 0.2um")
via2.space(0.2, euclidian).output(
"via2.2",
"via2.2 : min. via2 spacing : 0.2um"
)
if SEAL
ringVIA2.width(0.2, euclidian).output(
"via2.3",
"via2.3 : min. width of ring-shaped via2 : 0.2um"
)
ringVIA2.drc(width(euclidian) >= 0.205).output(
"via2.3_a",
"via2.3_a : max. width of ring-shaped via2 : 0.205um"
)
ringVIA2.not(areaid_sl).output(
"via2.3_b",
"via2.3_b: ring-shaped via2 must be enclosed by areaid_sl"
)
end
m2.enclosing(via2, 0.04, euclidian).output(
"via2.4",
"via2.4 : min. m2 enclosure of via2 : 0.04um"
)
via2.not(m2).output("MR_via2.CON.1", "MR_via2.CON.1 : via must be enclosed by met2")
via2_edges_with_less_enclosure =
m2.enclosing(via2, 0.085, projection).second_edges
error_corners =
via2_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu)
via2_interact = via2.interacting(error_corners.polygons(1.dbu))
via2_interact.output(
"via2.5",
"via2.5 : min. m3 enclosure of via2 of 2 adjacent edges : 0.085um"
)
end
log("END: 69/44 (via2)")
# m3
log("START: 70/20 (m3)")
m3.width(0.3, euclidian).output("MR_met3.WID.1", "MR_met3.WID.1 : min. m3 width : 0.3um")
huge_m3 = m3.sized(-1.5).sized(1.5).snap(0.005) & m3
non_huge_m3 = m3.edges - huge_m3
huge_m3 = huge_m3.edges.outside_part(m3.merged)
non_huge_m3.space(0.3, euclidian).output(
"m3.2",
"m3.2 : min. m3 spacing : 0.3um"
)
(
huge_m3.separation(non_huge_m3, 0.4, euclidian) +
huge_m3.space(0.4, euclidian)
).output("MRW_met3.SP.2", "MRW_met3.SP.2 : min. 3um.m3 spacing m3 : 0.4um")
if FLOATING_MET
m3.not_interacting(via2.or(via3)).output(
"m3.x",
"floating met3, must interact with via2 or via3"
)
end
if backend_flow == AL
m3.enclosing(via2, 0.065, euclidian).output(
"m3.4",
"m3.4 : min. m3 enclosure of via2 : 0.065um"
)
via2.not(m3).output("MR_met3.CON.1", "MR_met3.CON.1 : via2 must be enclosed by met3")
end
m3.with_area(0..0.240).output("MR_met3.AR.1", "MR_met3.AR.1 : min. m3 area : 0.240um²")
m3
.holes
.with_area(0..0.2)
.output("MR_met3.AR.2", "MR_met3.AR.2 : min. m3 holes area : 0.2um²")
log("END: 70/20 (m3)")
# via3
log("START: 70/44 (via3)")
if backend_flow == AL
if SEAL
ringVIA3 = via3.drc(with_holes > 0)
rectVIA3 = via3.not(ringVIA3)
else
rectVIA3 = via3
end
via3_not_mt = rectVIA3.not(areaid_mt)
via3_not_mt.non_rectangles.output(
"via3.1",
"via3.1 : via3 outside of moduleCut should be rectangular"
)
via3_not_mt.width(0.2, euclidian).output(
"via3.1_a",
"via3.1_a : min. width of via3 outside of moduleCut : 0.2um"
)
via3_not_mt
.edges
.without_length(nil, 0.2 + 1.dbu)
.output("MR_via3.LEN.1", "MR_via3.LEN.1 : maximum length of via3 : 0.2um")
via3.space(0.2, euclidian).output(
"via3.2",
"via3.2 : min. via3 spacing : 0.2um"
)
m3.enclosing(via3, 0.06, euclidian).output(
"via3.4",
"via3.4 : min. m3 enclosure of via3 : 0.06um"
)
rectVIA3.not(m3).output(
"via3.4_a",
"via3.4_a : non-ring via3 must be enclosed by met3"
)
via_edges_with_less_enclosure =
m3.enclosing(via3, 0.09, projection).second_edges
error_corners =
via_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu)
via3_interact = via3.interacting(error_corners.polygons(1.dbu))
via3_interact.output(
"via3.5",
"via3.5 : min. m3 enclosure of via3 of 2 adjacent edges : 0.09um"
)
end
log("END: 70/44 (via3)")
# m4
log("START: 71/20 (m4)")
m4.width(0.3, euclidian).output("MR_met4.WID.1", "MR_met4.WID.1 : min. m4 width : 0.3um")
huge_m4 = m4.sized(-1.5).sized(1.5).snap(0.005) & m4
non_huge_m4 = m4.edges - huge_m4
huge_m4 = huge_m4.edges.outside_part(m4.merged)
non_huge_m4.space(0.3, euclidian).output(
"m4.2",
"m4.2 : min. m4 spacing : 0.3um"
)
m4.with_area(0..0.240).output("MR_met4.AR.1", "MR_met4.AR.1 : min. m4 area : 0.240um²")
(
huge_m4.separation(non_huge_m4, 0.4, euclidian) +
huge_m4.space(0.4, euclidian)
).output("MRW_met4.SP.2", "MRW_met4.SP.2 : min. 3um.m4 spacing m4 : 0.4um")
if FLOATING_MET
m4.not_interacting(via3.or(via4)).output(
"m4.x",
"floating met3, must interact with via3 or via4"
)
end
if backend_flow == AL
m4.enclosing(via3, 0.065, euclidian).output(
"m4.3",
"m4.3 : min. m4 enclosure of via3 : 0.065um"
)
via3.not(m4).output("MR_met4.CON.1", "MR_met4.CON.1 : via3 must be enclosed by met4")
end
m4
.holes
.with_area(0..0.2)
.output("MR_met4.AR.2", "MR_met4.AR.2 : min. m4 holes area : 0.2um²")
log("END: 71/20 (m4)")
# via4
log("START: 71/44 (via4)")
if SEAL
ringVIA4 = via4.drc(with_holes > 0)
rectVIA4 = via4.not(ringVIA4)
else
rectVIA4 = via4
end
via4_not_mt = rectVIA4.not(areaid_mt)
via4_not_mt.non_rectangles.output(
"via4.1",
"via4.1 : via4 outside of moduleCut should be rectangular"
)
rectVIA4.width(0.8, euclidian).output(
"via4.1_a",
"via4.1_a : min. width of via4 outside of moduleCut : 0.8um"
)
rectVIA4.drc(length > 0.8).output(
"via4.1_b",
"via4.1_b : maximum length of via4 : 0.8um"
)
via4
.space(0.8, euclidian)
.polygons
.output("MR_via4.SP.1", "MR_via4.SP.1 : min. via4 spacing : 0.8um")
if SEAL
ringVIA4.width(0.8, euclidian).output(
"via4.3",
"via4.3 : min. width of ring-shaped via4 : 0.8um"
)
ringVIA4.drc(width(euclidian) >= 0.805).output(
"via4.3_a",
"via4.3_a : max. width of ring-shaped via4 : 0.805um"
)
ringVIA4.not(areaid_sl).output(
"via4.3_b",
"via4.3_b: ring-shaped via4 must be enclosed by areaid_sl"
)
end
m4.enclosing(via4, 0.19, euclidian).output(
"via4.4",
"via4.4 : min. m4 enclosure of via4 : 0.19um"
)
rectVIA4.not(m4).output("MR_via4.CON.1", "MR_via4.CON.1 : m4 must enclose all via4")
log("END: 71/44 (via4)")
# m5
log("START: 72/20 (m5)")
m5.width(1.6, euclidian).output("MR_met5.WID.1", "MR_met5.WID.1 : min. m5 width : 1.6um")
m5.space(1.6, euclidian).output("MR_met5.SP.1", "MR_met5.SP.1 : min. m5 spacing : 1.6um")
m5.enclosing(via4, 0.31, euclidian).output(
"m5.3",
"m5.3 : min. m5 enclosure of via4 : 0.31um"
)
via4.not(m5).output("MR_met5.CON.1", "MR_met5.CON.1 : via must be enclosed by m5")
if FLOATING_MET
m5.not_interacting(via4).output(
"m5.x",
"floating met5, must interact with via4"
)
end
m5.with_area(0..4.0).output("MR_met5.AR.1", "MR_met5.AR.1 : min. m5 area : 4.0um²")
m5
.holes
.with_area(0..0.14)
.output("MR_met5.AR.2", "MR_met5.AR.2 : min. m5 holes area : 0.14um²")
log("END: 72/20 (m5)")
# pad
log("START: 76/20 (pad)")
pad.space(1.27, euclidian).output(
"pad.2",
"pad.2 : min. pad spacing : 1.27um"
)
log("END: 76/20 (pad)")
log("START: 81/10 (moduleCut)")
areaid_mt.output(
"moduleCut.1",
"moduleCut.1 : moduleCut layer is for SkyWater use only"
)
log("END: 81/10 (moduleCut)")
#areaid_mt = polygons(81, 10)
end #BEOL
if FEOL
log("FEOL section")
# hvi
log("START: 75/20 (hvi)")
hvi_peri = hvi.not(areaid_ce)
hvi_peri.width(0.6, euclidian).output(
"hvi.1",
"hvi.1 : min. hvi width : 0.6um"
)
hvi_peri.space(0.7, euclidian).output(
"hvi.2a",
"hvi.2a : min. hvi spacing : 0.7um"
)
log("END: 75/20 (hvi)")
# hvntm
log("START: 125/20 (hvntm)")
hvntm_peri = hvntm.not(areaid_ce)
hvntm_peri.width(0.7, euclidian).output(
"hvntm.1",
"hvntm.1 : min. hvntm width : 0.7um"
)
hvntm_peri.space(0.7, euclidian).output(
"hvntm.2",
"hvntm.2 : min. hvntm spacing : 0.7um"
)
log("END: 125/20 (hvntm)")
end #FEOL
if OFFGRID
log("OFFGRID-ANGLES section")
dnwell.ongrid(0.005).output(
"dnwell_OFFGRID",
"x.1b : OFFGRID vertex on dnwell"
)
dnwell.with_angle(0..45).output(
"dnwell_angle",
"x.3a : non 45 degree angle dnwell"
)
nwell.ongrid(0.005).output("nwell_OFFGRID", "x.1b : OFFGRID vertex on nwell")
nwell.with_angle(0..45).output(
"nwell_angle",
"x.3a : non 45 degree angle nwell"
)
pwbm.ongrid(0.005).output("pwbm_OFFGRID", "x.1b : OFFGRID vertex on pwbm")
pwbm.with_angle(0..45).output("pwbm_angle", "x.3a : non 45 degree angle pwbm")
pwde.ongrid(0.005).output("pwde_OFFGRID", "x.1b : OFFGRID vertex on pwde")
pwde.with_angle(0..45).output("pwde_angle", "x.3a : non 45 degree angle pwde")
hvtp.ongrid(0.005).output("hvtp_OFFGRID", "x.1b : OFFGRID vertex on hvtp")
hvtp.with_angle(0..45).output("hvtp_angle", "x.3a : non 45 degree angle hvtp")
hvtr.ongrid(0.005).output("hvtr_OFFGRID", "x.1b : OFFGRID vertex on hvtr")
hvtr.with_angle(0..45).output("hvtr_angle", "x.3a : non 45 degree angle hvtr")
lvtn.ongrid(0.005).output("lvtn_OFFGRID", "x.1b : OFFGRID vertex on lvtn")
lvtn.with_angle(0..45).output("lvtn_angle", "x.3a : non 45 degree angle lvtn")
ncm.ongrid(0.005).output("ncm_OFFGRID", "x.1b : OFFGRID vertex on ncm")
ncm.with_angle(0..45).output("ncm_angle", "x.3a : non 45 degree angle ncm")
diff.ongrid(0.005).output("diff_OFFGRID", "x.1b : OFFGRID vertex on diff")
tap.ongrid(0.005).output("tap_OFFGRID", "x.1b : OFFGRID vertex on tap")
diff
.not(areaid_en.and(uhvi))
.with_angle(0..90)
.output("diff_angle", "x.2 : non 90 degree angle diff")
diff
.and(areaid_en.and(uhvi))
.with_angle(0..45)
.output("diff_angle", "x.2c : non 45 degree angle diff")
tap
.not(areaid_en.and(uhvi))
.with_angle(0..90)
.output("tap_angle", "x.2 : non 90 degree angle tap")
tap
.and(areaid_en.and(uhvi))
.with_angle(0..45)
.output("tap_angle", "x.2c : non 45 degree angle tap")
tunm.ongrid(0.005).output("tunm_OFFGRID", "x.1b : OFFGRID vertex on tunm")
tunm.with_angle(0..45).output("tunm_angle", "x.3a : non 45 degree angle tunm")
poly.ongrid(0.005).output("poly_OFFGRID", "x.1b : OFFGRID vertex on poly")
poly.with_angle(0..90).output("poly_angle", "x.2 : non 90 degree angle poly")
rpm.ongrid(0.005).output("rpm_OFFGRID", "x.1b : OFFGRID vertex on rpm")
rpm.with_angle(0..45).output("rpm_angle", "x.3a : non 45 degree angle rpm")
npc.ongrid(0.005).output("npc_OFFGRID", "x.1b : OFFGRID vertex on npc")
npc.with_angle(0..45).output("npc_angle", "x.3a : non 45 degree angle npc")
nsdm.ongrid(0.005).output("nsdm_OFFGRID", "x.1b : OFFGRID vertex on nsdm")
nsdm.with_angle(0..45).output("nsdm_angle", "x.3a : non 45 degree angle nsdm")
psdm.ongrid(0.005).output("psdm_OFFGRID", "x.1b : OFFGRID vertex on psdm")
psdm.with_angle(0..45).output("psdm_angle", "x.3a : non 45 degree angle psdm")
licon.ongrid(0.005).output("licon_OFFGRID", "x.1b : OFFGRID vertex on licon")
licon.with_angle(0..90).output(
"licon_angle",
"x.2 : non 90 degree angle licon"
)
li.ongrid(0.005).output("li_OFFGRID", "x.1b : OFFGRID vertex on li")
li.with_angle(0..45).output("li_angle", "x.3a : non 45 degree angle li")
mcon.ongrid(0.005).output("ct_OFFGRID", "x.1b : OFFGRID vertex on mcon")
mcon.with_angle(0..90).output("ct_angle", "x.2 : non 90 degree angle mcon")
vpp.ongrid(0.005).output("vpp_OFFGRID", "x.1b : OFFGRID vertex on vpp")
vpp.with_angle(0..45).output("vpp_angle", "x.3a : non 45 degree angle vpp")
m1.ongrid(0.005).output("m1_OFFGRID", "x.1b : OFFGRID vertex on m1")
m1.with_angle(0..45).output("m1_angle", "x.3a : non 45 degree angle m1")
via.ongrid(0.005).output("via_OFFGRID", "x.1b : OFFGRID vertex on via")
via.with_angle(0..90).output("via_angle", "x.2 : non 90 degree angle via")
m2.ongrid(0.005).output("m2_OFFGRID", "x.1b : OFFGRID vertex on m2")
m2.with_angle(0..45).output("m2_angle", "x.3a : non 45 degree angle m2")
via2.ongrid(0.005).output("via2_OFFGRID", "x.1b : OFFGRID vertex on via2")
via2.with_angle(0..90).output("via2_angle", "x.2 : non 90 degree angle via2")
m3.ongrid(0.005).output("m3_OFFGRID", "x.1b : OFFGRID vertex on m3")
m3.with_angle(0..45).output("m3_angle", "x.3a : non 45 degree angle m3")
via3.ongrid(0.005).output("via3_OFFGRID", "x.1b : OFFGRID vertex on via3")
via3.with_angle(0..90).output("via3_angle", "x.2 : non 90 degree angle via3")
nsm.ongrid(0.005).output("nsm_OFFGRID", "x.1b : OFFGRID vertex on nsm")
nsm.with_angle(0..45).output("nsm_angle", "x.3a : non 45 degree angle nsm")
m4.ongrid(0.005).output("m4_OFFGRID", "x.1b : OFFGRID vertex on m4")
m4.with_angle(0..45).output("m4_angle", "x.3a : non 45 degree angle m4")
via4.ongrid(0.005).output("via4_OFFGRID", "x.1b : OFFGRID vertex on via4")
via4.with_angle(0..90).output("via4_angle", "x.2 : non 90 degree angle via4")
m5.ongrid(0.005).output("m5_OFFGRID", "x.1b : OFFGRID vertex on m5")
m5.with_angle(0..45).output("m5_angle", "x.3a : non 45 degree angle m5")
pad.ongrid(0.005).output("pad_OFFGRID", "x.1b : OFFGRID vertex on pad")
pad.with_angle(0..45).output("pad_angle", "x.3a : non 45 degree angle pad")
mf.ongrid(0.005).output("mf_OFFGRID", "x.1b : OFFGRID vertex on mf")
mf.with_angle(0..90).output("mf_angle", "x.2 : non 90 degree angle mf")
hvi.ongrid(0.005).output("hvi_OFFGRID", "x.1b : OFFGRID vertex on hvi")
hvi.with_angle(0..45).output("hvi_angle", "x.3a : non 45 degree angle hvi")
hvntm.ongrid(0.005).output("hvntm_OFFGRID", "x.1b : OFFGRID vertex on hvntm")
hvntm.with_angle(0..45).output(
"hvntm_angle",
"x.3a : non 45 degree angle hvntm"
)
vhvi.ongrid(0.005).output("vhvi_OFFGRID", "x.1b : OFFGRID vertex on vhvi")
vhvi.with_angle(0..45).output("vhvi_angle", "x.3a : non 45 degree angle vhvi")
uhvi.ongrid(0.005).output("uhvi_OFFGRID", "x.1b : OFFGRID vertex on uhvi")
uhvi.with_angle(0..45).output("uhvi_angle", "x.3a : non 45 degree angle uhvi")
pwell_rs.ongrid(0.005).output(
"pwell_rs_OFFGRID",
"x.1b : OFFGRID vertex on pwell_rs"
)
pwell_rs.with_angle(0..45).output(
"pwell_rs_angle",
"x.3a : non 45 degree angle pwell_rs"
)
areaid_re.ongrid(0.005).output(
"areaid_re_OFFGRID",
"x.1b : OFFGRID vertex on areaid.re"
)
end #OFFGRID
logger.info(" ")
logger.info("Cell exclusion list:")
logger.info(" rule | cell")
if SRAM_EXCLUDE
logger.info(
" nwell.6 | sky130_fd_io__gpiov2_amux, sky130_fd_io__simple_pad_and_busses, sram"
)
logger.info(" nsd.1 | sram")
logger.info(" nsd.2 | sram")
logger.info(" psd.1 | sram")
logger.info(" psd.2 | sram")
else
logger.info(
" nwell.6 | sky130_fd_io__gpiov2_amux, sky130_fd_io__simple_pad_and_busses"
)
end #SRAM_EXCLUDE
logger.info(" ")
logger.info("release #{release}")