technology.py
1 # SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-or-later OR CERN-OHL-S-2.0+ OR Apache-2.0 2 # type: ignore 3 import unittest 4 5 # Use mainly dummy_tech for checking and code coverage 6 from ..dummy import dummy_tech 7 dummy_prims = dummy_tech.primitives 8 9 from pdkmaster.technology import ( 10 property_ as _prp, geometry as _geo, primitive as _prm, technology_ as _tch, 11 ) 12 13 class TechnologyTest(unittest.TestCase): 14 def test_error(self): 15 with self.assertRaises(ValueError): 16 # no Base primitive 17 class ErrorTech(_tch.Technology): 18 def name(self): 19 return "error" 20 def grid(self): 21 return 0.005 22 23 def __init__(self): 24 super().__init__(primitives=_prm.Primitives()) 25 ErrorTech() 26 27 with self.assertRaises(ValueError): 28 # base not of type Base 29 class ErrorTech(_tch.Technology): 30 def name(self): 31 return "error" 32 def grid(self): 33 return 0.005 34 35 def __init__(self): 36 super().__init__(primitives=_prm.Primitives( 37 _prm.Auxiliary(name="base") 38 )) 39 ErrorTech() 40 41 def test_rules(self): 42 # This is mainly a test for coverage 43 self.assertEqual(len(dummy_tech.rules), 161) 44 45 def test_computed(self): 46 nwell = dummy_prims.nwell 47 active = dummy_prims.active 48 nplus = dummy_prims.nplus 49 pplus = dummy_prims.pplus 50 hvox = dummy_prims.hvox 51 poly = dummy_prims.poly 52 contact = dummy_prims.contact 53 metal = dummy_prims.metal 54 via = dummy_prims.via 55 56 # min_space 57 nenc = active.min_implant_enclosure[active.implant.index(nplus)].min() 58 penc = active.min_implant_enclosure[active.implant.index(pplus)].min() 59 nwenc = active.min_well_enclosure[active.well.index(nwell)].min() 60 hvoxenc = active.min_oxide_enclosure[active.oxide.index(hvox)].min() 61 hvoxspace = dummy_tech.computed.min_space(hvox, active) 62 63 self.assertAlmostEqual( 64 dummy_tech.computed.min_space(primitive1=active), 65 active.min_space, 66 places=6, 67 ) 68 self.assertAlmostEqual( 69 dummy_tech.computed.min_space(primitive1=active.in_(nplus)), 70 active.min_space, 71 places=6, 72 ) 73 self.assertAlmostEqual( 74 dummy_tech.computed.min_space( 75 primitive1=active, primitive2=active.in_((pplus, hvox, nwell)), 76 ), 77 hvoxenc + hvoxspace, 78 places=6, 79 ) 80 self.assertAlmostEqual( 81 dummy_tech.computed.min_space( 82 primitive1=active.in_(nplus), primitive2=active, 83 ), 84 active.min_space, 85 places=6, 86 ) 87 self.assertAlmostEqual( 88 dummy_tech.computed.min_space( 89 primitive1=active.in_(nplus), primitive2=active.in_(pplus), 90 ), 91 nenc + penc, 92 places=6, 93 ) 94 self.assertAlmostEqual( 95 dummy_tech.computed.min_space( 96 primitive1=active.in_(nplus), 97 primitive2=active.in_((pplus, nwell, hvox)), 98 ), 99 nenc + max(penc, nwenc, hvoxenc), 100 places=6, 101 ) 102 self.assertAlmostEqual( 103 dummy_tech.computed.min_space( 104 primitive1=metal, width=0.5, 105 ), 106 0.5, places=6, 107 ) 108 self.assertAlmostEqual( 109 dummy_tech.computed.min_space( 110 primitive1=metal, width=1.1, 111 ), 112 1.0, places=6, 113 ) 114 with self.assertRaises(AttributeError): 115 # No min_space between active and poly 116 dummy_tech.computed.min_space( 117 primitive1=active, primitive2=poly 118 ) 119 with self.assertRaises(TypeError): 120 # width provided and primitive1 is derived layer 121 dummy_tech.computed.min_space( 122 primitive1=active.in_(nplus), width=1.0, 123 ) 124 with self.assertRaises(TypeError): 125 # width provided and also primitive2 126 dummy_tech.computed.min_space( 127 primitive1=active, primitive2=poly, width=1.0, 128 ) 129 130 # min_width 131 self.assertAlmostEqual( 132 dummy_tech.computed.min_width(primitive=active), 133 active.min_width, 134 places=6, 135 ) 136 self.assertAlmostEqual( 137 dummy_tech.computed.min_width( 138 primitive=metal, up=True, down=True, 139 ), 140 max( 141 metal.min_width, 142 contact.width + 2*contact.min_top_enclosure[0].max(), 143 via.width + 2*via.min_bottom_enclosure[0].max(), 144 ), 145 places=6, 146 ) 147 148 # min_pitch 149 self.assertAlmostEqual( 150 dummy_tech.computed.min_pitch(primitive=active), 151 active.min_width + active.min_space, 152 places=6, 153 ) 154 self.assertAlmostEqual( 155 dummy_tech.computed.min_pitch( 156 primitive=metal, up=True, down=True, 157 ), 158 max( 159 metal.min_width, 160 contact.width + 2*contact.min_top_enclosure[0].max(), 161 via.width + 2*via.min_bottom_enclosure[0].max(), 162 ) + metal.min_space, 163 places=6, 164 ) 165 166 def test_ongrid(self): 167 grid = dummy_tech.grid 168 169 # is_ongrid() method 170 self.assertTrue(dummy_tech.is_ongrid(grid)) 171 self.assertFalse(dummy_tech.is_ongrid(grid/2)) 172 173 # on_grid() method 174 with self.assertRaises(ValueError): 175 # Wrong rounding specification 176 dummy_tech.on_grid(1.1*grid, rounding="error") 177 178 self.assertAlmostEqual( 179 dummy_tech.on_grid(1.1*grid, rounding="nearest"), 180 grid, 181 places=6, 182 ) 183 self.assertAlmostEqual( 184 dummy_tech.on_grid(1.1*grid, rounding="floor"), 185 grid, 186 places=6, 187 ) 188 self.assertAlmostEqual( 189 dummy_tech.on_grid(1.1*grid, rounding="ceiling"), 190 2*grid, 191 places=6, 192 ) 193 self.assertAlmostEqual( 194 dummy_tech.on_grid(3.1*grid, mult=2, rounding="nearest"), 195 4*grid, 196 places=6, 197 ) 198 199 self.assertEqual( 200 dummy_tech.on_grid(_geo.Point(x=1.1*grid, y=1.1*grid)), 201 _geo.Point(x=grid, y=grid), 202 ) 203 204 def test_dbu1(self): 205 self.assertAlmostEqual(dummy_tech.dbu, 1e-3, delta=_geo.epsilon) 206 207 class MyTech(_tch.Technology): 208 @property 209 def name(self) -> str: 210 return "MyTech" 211 @property 212 def grid(self) -> float: 213 return 2.5e-3 214 215 def __init__(self): 216 super().__init__(primitives=_prm.Primitives( 217 _prm.Base(type_=_prm.pBase), 218 )) 219 mytech = MyTech() 220 self.assertAlmostEqual(mytech.dbu, 1e-4, delta=_geo.epsilon) 221 222 class MyTech(_tch.Technology): 223 @property 224 def name(self) -> str: 225 return "MyTech" 226 @property 227 def grid(self) -> float: 228 return 1.25e-3 229 230 def __init__(self): 231 super().__init__(primitives=_prm.Primitives( 232 _prm.Base(type_=_prm.pBase), 233 )) 234 mytech = MyTech() 235 self.assertAlmostEqual(mytech.dbu, 1e-5, delta=_geo.epsilon) 236 237 def test_padopening(self): 238 class MyTech(_tch.Technology): 239 @property 240 def name(self) -> str: 241 return "MyTech" 242 @property 243 def grid(self) -> float: 244 return 5e-3 245 246 def __init__(self): 247 prims = _prm.Primitives(_prm.Base(type_=_prm.pBase)) 248 249 metal1 = _prm.MetalWire(name="metal1", min_width=1.0, min_space=1.0) 250 pad = _prm.PadOpening( 251 name="pad", min_width=20.0, min_space=3.0, 252 bottom=metal1, min_bottom_enclosure=2.0, 253 ) 254 prims += (metal1, pad) 255 256 super().__init__(primitives=prims) 257 # Code coverage only 258 MyTech() 259 260 def test_substrate(self): 261 self.assertEqual( 262 dummy_tech.substrate_prim, 263 dummy_prims.base.remove(dummy_prims.active.well).alias("substrate:Dummy"), 264 ) 265 266 def test_mosfet(self): 267 nmos = dummy_prims.nmos 268 prim = _prm._derived._Intersect(prims=(nmos.gate, *nmos.implant)).remove(nmos.gate.active.well) 269 alias = prim.alias("gate:mosfet:nmos") 270 self.assertEqual(nmos.gate4mosfet._prim, prim) 271 self.assertEqual(nmos.gate4mosfet, alias) 272 273 def test_check(self): 274 class MyTech(_tch.Technology): 275 @property 276 def name(self) -> str: 277 return "MyTech" 278 @property 279 def grid(self) -> float: 280 return 2.5e-3 281 282 def __init__(self): 283 prims = _prm.Primitives(_prm.Base(type_=_prm.pBase)) 284 285 prims += _prm.Well( 286 name="well", min_width=5.0, min_space=3.0, type_=_prm.nImpl, 287 ) 288 289 super().__init__(primitives=prims) 290 with self.assertRaises(_prm.UnconnectedPrimitiveError): 291 MyTech() 292 293 class MyTech(_tch.Technology): 294 @property 295 def name(self) -> str: 296 return "MyTech" 297 @property 298 def grid(self) -> float: 299 return 5e-3 300 301 def __init__(self): 302 prims = _prm.Primitives(_prm.Base(type_=_prm.pBase)) 303 304 metal1 = _prm.MetalWire(name="metal1", min_width=1.0, min_space=1.0) 305 prims += metal1 306 307 super().__init__(primitives=prims) 308 with self.assertRaises(_prm.UnusedPrimitiveError): 309 MyTech() 310 311 class MyTech(_tch.Technology): 312 @property 313 def name(self) -> str: 314 return "MyTech" 315 @property 316 def grid(self) -> float: 317 return 5e-3 318 319 def __init__(self): 320 prims = _prm.Primitives(_prm.Base(type_=_prm.pBase)) 321 322 nimpl = _prm.Implant( 323 name="nimplant", min_width=1.2, min_space=1.2, type_=_prm.nImpl, 324 ) 325 pimpl = _prm.Implant( 326 name="pimplant", min_width=1.2, min_space=1.2, type_=_prm.pImpl, 327 ) 328 nwell = _prm.Well( 329 name="nwell", min_width=5.0, min_space=3.0, type_=_prm.nImpl, 330 ) 331 prims += (nimpl, pimpl, nwell) 332 333 active = _prm.WaferWire( 334 name="active", min_width=0.8, min_space=0.8, 335 implant=(nimpl, pimpl), min_implant_enclosure=_prp.Enclosure(0.2), 336 implant_abut="all", allow_contactless_implant=False, 337 allow_in_substrate=True, 338 well=nwell, min_well_enclosure=_prp.Enclosure(0.8), 339 allow_well_crossing=False, 340 ) 341 poly = _prm.GateWire(name="poly", min_width=0.8, min_space=0.8) 342 prims += (active, poly) 343 344 gate = _prm.MOSFETGate( 345 active=active, poly=poly, 346 ) 347 prims += gate 348 349 super().__init__(primitives=prims) 350 with self.assertRaises(_prm.UnusedPrimitiveError): 351 MyTech() 352 353 class MyTech(_tch.Technology): 354 @property 355 def name(self) -> str: 356 return "MyTech" 357 @property 358 def grid(self) -> float: 359 return 5e-3 360 361 def __init__(self): 362 prims = _prm.Primitives(_prm.Base(type_=_prm.pBase)) 363 364 nimpl = _prm.Implant( 365 name="nimplant", min_width=1.2, min_space=1.2, type_=_prm.nImpl, 366 ) 367 pimpl = _prm.Implant( 368 name="pimplant", min_width=1.2, min_space=1.2, type_=_prm.pImpl, 369 ) 370 nwell = _prm.Well( 371 name="nwell", min_width=5.0, min_space=3.0, type_=_prm.nImpl, 372 ) 373 prims += (nimpl, pimpl, nwell) 374 375 active = _prm.WaferWire( 376 name="active", min_width=0.8, min_space=0.8, 377 implant=(nimpl, pimpl), min_implant_enclosure=_prp.Enclosure(0.2), 378 implant_abut="all", allow_contactless_implant=False, 379 allow_in_substrate=True, 380 well=nwell, min_well_enclosure=_prp.Enclosure(0.8), 381 allow_well_crossing=False, 382 ) 383 poly = _prm.GateWire(name="poly", min_width=0.8, min_space=0.8) 384 metal1 = _prm.MetalWire(name="metal1", min_width=1.0, min_space=1.0) 385 contact = _prm.Via( 386 name="contact", width=0.8, min_space=0.8, 387 bottom=poly, min_bottom_enclosure=_prp.Enclosure(0.0), 388 top=metal1, min_top_enclosure=_prp.Enclosure(0.2), 389 ) 390 prims += (active, poly, contact, metal1) 391 392 gate = _prm.MOSFETGate( 393 active=active, poly=poly, 394 min_sd_width=0.35, min_polyactive_extension=0.35, 395 ) 396 nmos = _prm.MOSFET( 397 name="nmos", gate=gate, implant=nimpl, 398 min_gateimplant_enclosure=_prp.Enclosure(0.3), 399 ) 400 prims += (gate, nmos) 401 402 super().__init__(primitives=prims) 403 with self.assertRaises(_tch.Technology.ConnectionError): 404 MyTech() 405 406 class MyTech(_tch.Technology): 407 @property 408 def name(self) -> str: 409 return "MyTech" 410 @property 411 def grid(self) -> float: 412 return 5e-3 413 414 def __init__(self): 415 prims = _prm.Primitives(_prm.Base(type_=_prm.pBase)) 416 417 nimpl = _prm.Implant( 418 name="nimplant", min_width=1.2, min_space=1.2, type_=_prm.nImpl, 419 ) 420 pimpl = _prm.Implant( 421 name="pimplant", min_width=1.2, min_space=1.2, type_=_prm.pImpl, 422 ) 423 nwell = _prm.Well( 424 name="nwell", min_width=5.0, min_space=3.0, type_=_prm.nImpl, 425 ) 426 prims += (nimpl, pimpl, nwell) 427 428 active = _prm.WaferWire( 429 name="active", min_width=0.8, min_space=0.8, 430 implant=nimpl, min_implant_enclosure=_prp.Enclosure(0.2), 431 implant_abut="all", allow_contactless_implant=False, 432 allow_in_substrate=True, 433 well=nwell, min_well_enclosure=_prp.Enclosure(0.8), 434 allow_well_crossing=False, 435 ) 436 poly = _prm.GateWire(name="poly", min_width=0.8, min_space=0.8) 437 metal1 = _prm.MetalWire(name="metal1", min_width=1.0, min_space=1.0) 438 contact = _prm.Via( 439 name="contact", width=0.8, min_space=0.8, 440 bottom=(active, poly), min_bottom_enclosure=_prp.Enclosure(0.0), 441 top=metal1, min_top_enclosure=_prp.Enclosure(0.2), 442 ) 443 prims += (active, poly, contact, metal1) 444 445 gate = _prm.MOSFETGate( 446 active=active, poly=poly, 447 min_sd_width=0.35, min_polyactive_extension=0.35, 448 ) 449 nmos_impl = _prm.MOSFET( 450 name="nmos_impl", gate=gate, implant=nimpl, 451 min_gateimplant_enclosure=_prp.Enclosure(0.3), 452 ) 453 nmos_noimpl = _prm.MOSFET( 454 name="nmos_noimpl", gate=gate, implant=(), 455 min_gateimplant_enclosure=(), 456 ) 457 prims += (gate, nmos_impl, nmos_noimpl) 458 459 super().__init__(primitives=prims) 460 with self.assertRaises(ValueError): 461 MyTech() 462 463 class MyTech(_tch.Technology): 464 @property 465 def name(self) -> str: 466 return "MyTech" 467 @property 468 def grid(self) -> float: 469 return 5e-3 470 471 def __init__(self): 472 prims = _prm.Primitives(_prm.Base(type_=_prm.pBase)) 473 474 metal1 = _prm.MetalWire(name="metal1", min_width=1.0, min_space=1.0) 475 metal2 = _prm.MetalWire(name="metal1", min_width=1.0, min_space=1.0) 476 via = _prm.Via( 477 name="via", width=0.8, min_space=0.8, 478 bottom=metal1, min_bottom_enclosure=_prp.Enclosure(0.2), 479 top=metal2, min_top_enclosure=_prp.Enclosure(0.2), 480 ) 481 prims += (metal1, via, metal2) 482 483 super().__init__(primitives=prims) 484 with self.assertRaises(_tch.Technology.ConnectionError): 485 MyTech() 486 487 class MyTech(_tch.Technology): 488 @property 489 def name(self) -> str: 490 return "MyTech" 491 @property 492 def grid(self) -> float: 493 return 5e-3 494 495 def __init__(self): 496 super().__init__(primitives=_prm.Primitives(( 497 _prm.Base(type_=_prm.pBase), 498 _prm.MIMTop(name="error", min_width=1.0, min_space=1.0), 499 ))) 500 with self.assertRaises(_prm.UnusedPrimitiveError): 501 MyTech() 502 503 # Accessing substrate_prim before primitives were added 504 class MyTech(_tch.Technology): 505 @property 506 def name(self) -> str: 507 return "MyTech" 508 @property 509 def grid(self) -> float: 510 return 5e-3 511 512 def __init__(self): 513 self.substrate_prim 514 with self.assertRaises(AttributeError): 515 MyTech()