/ test / unit / technology / technology.py
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()