1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
###############################################################################
# Copyright (c) 2015-2024 by Altair Engineering, Inc.
# All rights reserved.
#
# Altair Engineering, Inc. makes this software available as part of the Vision
# tool platform.  As long as you are a licensee of the Vision tool platform
# you may make copies of the software and modify it to be used within the
# Vision tool platform, but you must include all of this notice on any copy.
# Redistribution without written permission to any third party, with or
# without modification, is not permitted.
# Altair Engineering, Inc. does not warrant that this software is error free
# or fit for any purpose.  Altair Engineering, Inc. disclaims any liability for
# all claims, expenses, losses, damages and costs any user may incur as a
# result of using, copying or modifying the software.
# =============================================================================
#   @plugin
#       Prune SPF
#   @namespace
#       PruneSPF
#   @section
#       Modify the Loaded Database
#   @description
#       General purpose script to prune any kind of loaded netlist data.
#   @files
#       parasitic/pruneSPF.tcl
#   @example
#       demo/dspf/example.dspf
#   @cmdline
#       -readDSPF @example[0]
#       -userware @files[0]
#   @tag
#       spf zdb dspf parasitic
###############################################################################


# =============================================================================
# Init - Initialize the plugin.
# =============================================================================
#
proc PruneSPF:Init {} {
    global PruneSPF

    ##
    # Highlight the result in different colors.
    #
    set PruneSPF(Highlight) 1

    ##
    # Color for the actual path.
    #
    set PruneSPF(pathColor) 0

    ##
    # Color for the path to PG nets.
    #
    set PruneSPF(pgColor)   1

    ##
    # Color for the path to control inputs.
    #
    set PruneSPF(ctrlColor) 2

    ##
    # List of all highlighted elements, needed to reset color.
    #
    set PruneSPF(highlightedElements) {}

    ##
    # Extend the main menu and add entries to control the 'PruneSPF' script.
    #
    gui menu command                      \
        {"Prune SPF" "Save Cone as DSPF"} \
        {PruneSPF:WriteSpf dspf}

    gui menu command                      \
        {"Prune SPF" "Save Cone as SPEF"} \
        {PruneSPF:WriteSpf spef}

    ##
    # Add a checkbutton to the menu to control highlighting the results.
    #
    gui menu separator {"Prune SPF"} -sepId "pruneSPFSep1"

    gui menu checkbutton             \
        {"Prune SPF" "Highlight"}    \
        {PruneSPF:_toggleHighlight}  \
        PruneSPF(Highlight)

    gui menu command                              \
        {"Prune SPF" "Find Path between devices"} \
        {PruneSPF:FindDevicePathOfSelected}
}


# =============================================================================
# Finit - Finalize the plugin.
# =============================================================================
#
proc PruneSPF:Finit {} {
    set db [gui database get]
    PruneSPF:_resetHighlights $db

    ##
    # Remove menu entries.
    #
    gui menu removeEntry     {"Prune SPF" "Save Cone as DSPF"}
    gui menu removeEntry     {"Prune SPF" "Save Cone as SPEF"}
    gui menu removeEntry     {"Prune SPF" "Find Path between devices"}
    gui menu removeEntry     {"Prune SPF" "Highlight"}
    gui menu removeSeparator {"Prune SPF"} "pruneSPFSep1"

    ##
    # Reset global variables.
    #
    array unset PruneSPF
}


# -----------------------------------------------------------------------------
# _copyConeContents - Copy the contents of the Cone window into a new database.
# -----------------------------------------------------------------------------
#
proc PruneSPF:_copyConeContents {{binfile "-"}} {
    ##
    # Get the current database.
    #
    set db [gui database get]

    ##
    # Save the contents of the Cone Window as a binfile.
    #
    set     cmd [list gui cone saveAs]
    lappend cmd Binfile
    lappend cmd $binfile

    ##
    # Command options:
    #
    # lappend cmd -hierarchical
    # lappend cmd -commonCells
    # lappend cmd -portsForUnconnectedPins
    # lappend cmd -allTopPorts
    # lappend cmd -connectHiddenSupply
    # lappend cmd -unconnectedModulePorts
    #
    lappend cmd -topName [$db attr -db getValue "@DESIGN"]

    set coneDB [{*}$cmd]

    ##
    # Copy database root attributes to new DB.
    #
    {*} $coneDB attr -db set [$db attr -db get]

    return $coneDB
}


# -----------------------------------------------------------------------------
# _getParasiticModulesForConeContents -
# -----------------------------------------------------------------------------
#
proc _getParasiticModulesForConeContents {} {
    ##
    # Get the current database.
    #
    set db [gui database get]

    set paraModList {}

    ##
    # Only process net objects.
    #
    foreach oid [gui cone contents -type "net"] {
        ##
        # Convert the net OID into a signal OID.
        #
        set signal [$db oid convertTo signal $oid]

        ##
        # Skip net OIDs that cannot be converted into a parasitic module OID.
        #
        if {[catch {set paraMod [$db oid convertToParasitic $signal]}]} {
            continue
        }

        ##
        # Add this parasitic module to the result list.
        #
        lappend paraModList $paraMod
    }

    return $paraModList
}


# -----------------------------------------------------------------------------
# _fillParasitic - Fill contents to the parasitic modules for all signal
#                  loaded to the Cone window.
# -----------------------------------------------------------------------------
#
proc PruneSPF:_fillParasitic {db} {
    ##
    # Initialize a hash table storing the names of the parasitic modules
    # to fill sorted by the SPF type (DSPF or SPEF).
    #
    array set fillSpf {dspf {} spef {}}

    ##
    # Loop over the parasitic modules of all objects loaded to the
    # Cone window and add each parasitic module name to the fillSpf table.
    #
    foreach paraMod [_getParasiticModulesForConeContents] {
        $db spos foreach $paraMod fname begin end {
            set spfType [string tolower [$db spos filetype $fname]]
            lappend fillSpf($spfType) [$db oid oname $paraMod]
            break
        }
    }

    ##
    # Fill the parasitic contents for all signals loaded to the Cone window.
    #
    foreach spfType {dspf spef} {
        ##
        # Skip empty entries.
        #
        if {[llength $fillSpf($spfType)] == 0} {
            continue
        }

        ##
        # Create and evaluate the SPF command to fill the parasitic contents.
        #
        set spfCmd [list z$spfType -into $db]
        foreach name $fillSpf($spfType) {
            lappend spfCmd -fillParasitic $name
        }
        eval $spfCmd
    }
}


# -----------------------------------------------------------------------------
# _addParasitic - Match all parasitic files again to the pruned database.
# -----------------------------------------------------------------------------
#
proc PruneSPF:_addParasitic {pdb} {
    ##
    # Get the current database.
    #
    set db [gui database get]

    ##
    # Clear all red flags.
    #
    $db flag -db clear red

    ##
    # Loop over all objects loaded to the Cone window to find the
    # corresponding parasitic modules of the loaded signals.
    #
    foreach paraMod [_getParasiticModulesForConeContents] {
        ##
        # Convert the parasitic module OID into a signal OID.
        #
        set signal [$db oid convertFromParasitic $paraMod]

        ##
        # Search the corresponding signal in the pruned database.
        #
        if {[catch {set pSignal [$pdb oid create $signal]} m]} {
            continue
        }

        ##
        # Skip signals in the pruned database that already have the parasitic
        # module name attribute set.
        #
        set pModAttr [$pdb flatattr $pSignal getValue "@PARASITIC_MODULE_NAME"]
        if {$pModAttr != ""} {
            continue
        }

        ##
        # Add this parasitic module to the list of objects to copy.
        #
        $db flag $paraMod set red

        ##
        # Flag the contents to the parasitic module to be cloned.
        #
        $db foreach inst $paraMod inst {
            $db flag $inst set red
            $db foreach pin $inst pin  {
                $db flag $pin  set red
            }
            set pCellRef [$db oid down $inst]
            if {![$db flag $pCellRef is_set red]} {
                $db flag $pCellRef set red
                $db foreach port $pCellRef port {$db flag $port set red}
            }
        }
        $db foreach net $paraMod net {
            $db flag $net set red
        }

        ##
        # Only flag the ports of the parasitic module to be cloned if there
        # is a connection in the netlist database.
        #
        $pdb flat foreach pin -stopHier $pSignal pin {
            set name [$db get_para_port_name $pin]
            set paraPort [$db search port $paraMod $name]
            if {![$db oid isnull $paraPort]} {$db flag $paraPort set red}
        }

        ##
        # Add the corresponding flat attribute.
        #
        set paraModName [$db oid oname $paraMod]
        $pdb flatattr $pSignal set "@PARASITIC_MODULE_NAME=$paraModName"
    }

    ##
    # Clone the above flagged objects into the pruned database.
    #
    $db cloneDB -into $pdb \
        -keepcell red -keepinst red -keepnet red -keepport red
}


# -----------------------------------------------------------------------------
# _pruneCone -
# -----------------------------------------------------------------------------
#
proc PruneDSPF:_pruneCone {} {
    ##
    # Get the current database.
    #
    set db [gui database get]

    ##
    # Add Bulk pins to all transistor devices loaded to the Cone window.
    #
    set bulkList {}
    foreach oid [gui cone contents -type "inst"] {
        set bulk [$db getFuncPort $oid -noError -name "bulk"]
        if {$bulk == {}} {
            continue
        }
        lappend bulkList $bulk
        $db flag $bulk clear hide
    }
    gui database modified
    gui cone append $bulkList

    ##
    # Fill contents to the parasitic modules for all signal loaded
    # to the Cone window.
    #
    PruneSPF:_fillParasitic $db

    ##
    # Get the contents of the Cone window as a new in-memory database.
    #
    set pdb [PruneSPF:_copyConeContents]

    ##
    # Add the parasitics from the original database to the just created
    # pruned database.
    #
    PruneSPF:_addParasitic $pdb

    ##
    # Again set the hide flag at all bulk pins.
    #
    foreach bulk $bulkList {
        $db flag $bulk set hide
    }

    ##
    # Return the pruned database.
    #
    return $pdb
}


# =============================================================================
# WriteSpf - Write out the contents of the Cone as a SPF netlist.
# =============================================================================
#
proc PruneSPF:WriteSpf {spfFormat {filename ""}} {
    ##
    # Check for a supported target format.
    #
    switch -- $spfFormat {
        "spef" {
            set fTypes {{"SPEF File" {.spef}}}
            set title  "Save SPEF file"
        }
        "dspf" {
            set fTypes {{"DSPF File" {.dspf}}}
            set title  "Save DSPF file"
        }
        default {
            error "Wrong SPF format '$spfFormat' ('spef' or 'dspf' expected)."
        }
    }

    ##
    # If no filename was given then open a dialog to get the name of the
    # file to write.
    #
    if {$filename == ""} {
        set filename [gui window fileDialog saveFile $title $fTypes]
        if {($filename == "") || (![file exists $filename])} {
            return
        }
    }

    ##
    # Prune the database according to the objects loaded in the Cone windows.
    #
    set db [PruneDSPF:_pruneCone]

    ##
    # Write out the contents of the temporary database using the given
    # target SPF format.
    #
    $db write $spfFormat $filename

    ##
    # Close the temporary database.
    #
    $db close
}


# -----------------------------------------------------------------------------
# _addPathToCtrlPorts -
# -----------------------------------------------------------------------------
#
proc PruneSPF:_addPathToCtrlPorts {db gatePin} {
    set res [$db cone -in -targetIO $gatePin]
    return $res
}


# -----------------------------------------------------------------------------
# _toggleHighlight - Toggle highlight, if disabled, remove all highlights.
# -----------------------------------------------------------------------------
#
proc PruneSPF:_toggleHighlight {} {
    global PruneSPF

    if {!$PruneSPF(Highlight)} {
        set db [gui database get]
        PruneSPF:_resetHighlights $db
    }
}


# -----------------------------------------------------------------------------
# _doHighlight -
# -----------------------------------------------------------------------------
#
proc PruneSPF:_doHighlight {db oid color} {
    global PruneSPF

    ##
    # Return if highlight is not enabled.
    #
    if {!$PruneSPF(Highlight)} {
        return
    }

    ##
    # Return if there is already a highlight color set.
    #
    if {[$db hilight $oid get] != ""} {
        return
    }

    ##
    # Depending on the OID type either highlight the instance or in case of
    # a pin/port highlight the connected net.
    #
    switch -- [$db oid type $oid] {
        "port" -
        "pin"  {
            if {[$db isConnected $oid]} {
                set net [$db connectedNet $oid]
                if {![$db isPgNet $net]} {
                    $db hilight $net set $color
                    lappend PruneSPF(highlightedElements) $net
                }
            }
        }
        "inst" {
            $db hilight $oid set $color
            lappend PruneSPF(highlightedElements) $oid
        }
        default {}
    }
}


# -----------------------------------------------------------------------------
# _resetHighlights -
# -----------------------------------------------------------------------------
#
proc PruneSPF:_resetHighlights {db} {
    global PruneSPF

    ##
    # Delete all highlights of the elements.
    #
    set highlightsReset 0
    foreach oid $PruneSPF(highlightedElements) {
        $db hilight $oid delete -both
        set highlightsReset 1
    }

    ##
    # Reset list.
    #
    set PruneSPF(highlightedElements) {}

    if {$highlightsReset} {
        gui highlight changed
    }
}


# =============================================================================
# FindDevicePathOfSelected - Find device path between the selected elements.
# =============================================================================
#
proc PruneSPF:FindDevicePathOfSelected {} {
    set db [gui database get]

    PruneSPF:_resetHighlights $db
    set selectedOids [gui schem selection]

    if {[llength $selectedOids] != 2} {
        zmessage print WAR "Please select exactly two elements."
        return
    }

    PruneSPF:_findDevicePath [lindex $selectedOids 0] [lindex $selectedOids 1]
}


# -----------------------------------------------------------------------------
# _findDevicePath -
# -----------------------------------------------------------------------------
#
proc PruneSPF:_findDevicePath {startOid targetOid} {
    global PruneSPF

    ##
    # Get the current database.
    #
    set db [gui database get]

    ##
    # Extract the cone between the start and target OID.
    #
    zmessage print DBG "Call '$db cone -out -targetObj $targetOid $startOid'"
    set res [$db cone -out -targetObj $targetOid $startOid]
    if {$res == {}} {
        zmessage print DBG "Call '$db cone' again with flipped start/target."
        set res [$db cone -out -targetObj $startOid $targetOid]
    }

    set pathColor $PruneSPF(pathColor)

    set limit [gui settings get "cone:devicePathMax"]
    set totalList {}

    $db flag -db clear visit

    set loopCount 0
    while {1} {
        set openGateList {}

        zmessage print DBG \
            "Loop count: [incr loopCount], RES: [llength $res]"

        ##
        # Break the loop if the result list is empty.
        #
        if {$res == {}} {
            break
        }

        foreach oid $res {
            lappend totalList $oid

            PruneSPF:_doHighlight $db $oid $pathColor

            if {[$db oid type $oid] == "pin"} {
                set inst [$db oid convertTo inst $oid]
                $db flag $inst set visit

                PruneSPF:_doHighlight $db $inst $pathColor
            }
        }

        foreach oid $res {
            if {[$db oid type $oid] == "pin"} {
                ##
                # Calculate the path to power and ground.
                #
                zmessage print DBG \
                    "call coneToPG $oid"
                set pgPath [$db coneToPG $oid -opposite \
                                              -excludeFlaggedInst visit \
                                              -connlimit [expr {5 * $limit}] \
                                              -limit     $limit]
                zmessage print DBG \
                    "coneToPG RES: [llength $pgPath]"


                foreach pgp $pgPath {
                    lappend totalList $pgp

                    set inst [$db oid convertTo inst $pgp]

                    if {[$db flag $inst is visit]} {
                        continue
                    }

                    PruneSPF:_doHighlight $db $inst $PruneSPF(pgColor)
                    PruneSPF:_doHighlight $db $pgp  $PruneSPF(pgColor)

                    ##
                    # Store all gates in the path to power/ground.
                    #
                    set gatePin [$db getFuncPort $inst -noError -name gate]
                    if {($gatePin != {}) && (![$db flag $gatePin is visit])} {
                        $db flag $gatePin set visit
                        lappend openGateList $gatePin
                    }
                }
            }
        }

        zmessage print DBG \
            "openGateList RES: [llength $openGateList]"

        ##
        # Break the loop if there are no more open gate pins.
        #
        if {$openGateList == {}} {
            break
        }

        ##
        # Add the path to all control input ports.
        #
        set pathColor $PruneSPF(ctrlColor)
        set res {}
        foreach openGate $openGateList {
            zmessage print DBG \
                "call PruneSPF:_addPathToCtrlPorts $openGate"
            set ctrlPath [PruneSPF:_addPathToCtrlPorts $db $openGate]
            zmessage print DBG \
                "ctrl path RES: [llength $ctrlPath]"
            foreach oid $ctrlPath {
                lappend res $oid
            }
        }
    }

    ##
    # Load the result to the Cone window.
    #
    gui cone load $totalList
    gui window show Cone

    ##
    # Update Highlight in GUI.
    #
    if {$PruneSPF(Highlight)} {
        gui highlight changed
    }
}


# =============================================================================
# Call the initialization procedure.
# =============================================================================
#
PruneSPF:Init