sky130: use special_nfet_01v8 model for sub-0.42um nfets

The sky130 magic techfile splits nfet extraction on device width:

  device msubcircuit sky130_fd_pr__nfet_01v8 nfet,scnfet ... w>=0.42
  device msubcircuit sky130_fd_pr__special_nfet_01v8 scnfet ... w<0.42

ptx always emitted spice["nmos"], so every gate built from a
minimum-width device (drc["minwidth_tx"] = 0.36) was netlisted as
sky130_fd_pr__nfet_01v8 while magic extracted it as
sky130_fd_pr__special_nfet_01v8. netgen then reported a device class
mismatch in each such cell:

  Circuit 1: sky130_fd_pr__special_nfet_01v8 (1) | Circuit 2: (no matching element)
  Circuit 1: (no matching element)               | Circuit 2: sky130_fd_pr__nfet_01v8 (1)

The signed-off macros shipped in sky130A/libs.ref/sky130_sram_macros use
special_nfet_01v8 at this geometry, so this restores the naming those
were built with.

ptx now asks the technology for a narrow-device model, keyed by width.
Technologies that declare no such model are unaffected, and there is no
equivalent split for pfets so only the nmos is declared.

After this change pinv and pinv_dec report "Netlists match uniquely"
where they previously mismatched. Verified with ngspice that
special_nfet_01v8 resolves through sky130.lib.spice, so characterization
is unaffected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FcKSuKSchDEd7MqT8AffpN
This commit is contained in:
ThVerg 2026-08-11 14:56:03 +02:00
parent ed369f1af4
commit 1123f733bd
2 changed files with 32 additions and 4 deletions

View File

@ -116,12 +116,32 @@ class ptx(design):
# but this may be uncommented for debug purposes
# self.DRC()
def get_model_name(self):
"""
Return the device model name for this transistor's width.
Some technologies model narrow devices with a separate model. In
sky130, an nfet narrower than 0.42um is extracted as
sky130_fd_pr__special_nfet_01v8 rather than sky130_fd_pr__nfet_01v8
(see the "device msubcircuit" rules in sky130A.tech), so the netlist
must use the same name or LVS reports a device class mismatch on
every gate built from a minimum-width device.
"""
narrow_model = spice.get("{}_narrow".format(self.tx_type))
narrow_width = spice.get("{}_narrow_max_width".format(self.tx_type))
if narrow_model and narrow_width and self.tx_width < narrow_width:
return narrow_model
return spice[self.tx_type]
def create_netlist(self):
pin_list = ["D", "G", "S", "B"]
if self.tx_type == "nmos":
body_dir = "GROUND"
else:
body_dir = "POWER"
self.model_name = self.get_model_name()
dir_list = ["INOUT", "INPUT", "INOUT", body_dir]
self.add_pin_list(pin_list, dir_list)
@ -133,7 +153,7 @@ class ptx(design):
self.channel_length = drc("minlength_channel")
if cell_props.ptx.model_is_subckt:
# sky130
main_str = "X{{0}} {{1}} {0} m={1} w={2} l={3} ".format(spice[self.tx_type],
main_str = "X{{0}} {{1}} {0} m={1} w={2} l={3} ".format(self.model_name,
self.mults,
self.tx_width,
self.channel_length)
@ -142,7 +162,7 @@ class ptx(design):
area_str = "pd={0:.2f} ps={0:.2f} as={1:.2f}u ad={1:.2f}u".format(perimeter_sd,
area_sd)
else:
main_str = "M{{0}} {{1}} {0} m={1} w={2}u l={3}u ".format(spice[self.tx_type],
main_str = "M{{0}} {{1}} {0} m={1} w={2}u l={3}u ".format(self.model_name,
self.mults,
self.tx_width,
self.channel_length)
@ -164,12 +184,12 @@ class ptx(design):
self.tx_width,
self.channel_length)
elif cell_props.ptx.model_is_subckt:
self.lvs_device = "X{{0}} {{1}} {0} m={1} w={2}u l={3}u".format(spice[self.tx_type],
self.lvs_device = "X{{0}} {{1}} {0} m={1} w={2}u l={3}u".format(self.model_name,
self.mults,
self.tx_width,
self.channel_length)
else:
self.lvs_device = "M{{0}} {{1}} {0} m={1} w={2}u l={3}u ".format(spice[self.tx_type],
self.lvs_device = "M{{0}} {{1}} {0} m={1} w={2}u l={3}u ".format(self.model_name,
self.mults,
self.tx_width,
self.channel_length)

View File

@ -711,6 +711,14 @@ drc.add_enclosure("m5",
spice = {}
spice["nmos"] = "sky130_fd_pr__nfet_01v8"
spice["pmos"] = "sky130_fd_pr__pfet_01v8"
# An nfet narrower than 0.42um is a different device in sky130: magic
# extracts it as sky130_fd_pr__special_nfet_01v8, so the netlist has to use
# that name below the threshold or netgen reports a device class mismatch.
# See the "device msubcircuit" lines in sky130A.tech. There is no equivalent
# split for pfets, so only the nmos is declared here.
spice["nmos_narrow"] = "sky130_fd_pr__special_nfet_01v8"
spice["nmos_narrow_max_width"] = 0.42
spice["power"]="vccd1"
spice["ground"]="vssd1"