-
Notifications
You must be signed in to change notification settings - Fork 80
Expand file tree
/
Copy pathhammer_vlsi_impl.py
More file actions
1475 lines (1236 loc) · 63.9 KB
/
Copy pathhammer_vlsi_impl.py
File metadata and controls
1475 lines (1236 loc) · 63.9 KB
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
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# hammer_vlsi_impl.py
# hammer-vlsi implementation file. Users should import hammer_vlsi instead.
#
# See LICENSE for licence details.
from abc import abstractmethod
from enum import Enum
from functools import reduce
import importlib
from numbers import Number
import os
import sys
import json
from typing import Callable, Iterable, List, NamedTuple, Optional, Dict, Any, Union
from decimal import Decimal
import hammer_config
from hammer_utils import reverse_dict, deepdict, optional_map, get_or_else, add_dicts, coerce_to_grid
from hammer_tech import Library, ExtraLibrary
from .constraints import *
from .units import VoltageValue
class HierarchicalMode(Enum):
Flat = 1
Leaf = 2
Hierarchical = 3
Top = 4
@classmethod
def __mapping(cls) -> Dict[str, "HierarchicalMode"]:
return {
"flat": HierarchicalMode.Flat,
"leaf": HierarchicalMode.Leaf,
"hierarchical": HierarchicalMode.Hierarchical,
"top": HierarchicalMode.Top
}
@staticmethod
def from_str(x: str) -> "HierarchicalMode":
try:
return HierarchicalMode.__mapping()[x]
except KeyError:
raise ValueError("Invalid string for HierarchicalMode: " + str(x))
def __str__(self) -> str:
return reverse_dict(HierarchicalMode.__mapping())[self]
def is_nonleaf_hierarchical(self) -> bool:
"""
Helper function that returns True if this mode is a non-leaf hierarchical mode (i.e. any block with
hierarchical sub-blocks).
"""
return self == HierarchicalMode.Hierarchical or self == HierarchicalMode.Top
class HammerToolPauseException(Exception):
"""
Internal hammer-vlsi exception raised to indicate that a step has stopped execution of the tool.
This is not necessarily an error condition.
"""
pass
import hammer_tech
class HammerVLSISettings:
"""
Static class which holds global hammer-vlsi settings.
"""
hammer_vlsi_path = "" # type: str
@staticmethod
def get_config() -> dict:
"""Export settings as a config dictionary."""
return {
"vlsi.builtins.hammer_vlsi_path": HammerVLSISettings.hammer_vlsi_path
}
@classmethod
def set_hammer_vlsi_path_from_environment(cls) -> bool:
"""
Try to set hammer_vlsi_path from the environment variable HAMMER_VLSI.
:return: True if successfully set, False otherwise
"""
if "HAMMER_VLSI" not in os.environ:
return False
else:
cls.hammer_vlsi_path = os.environ["HAMMER_VLSI"]
return True
@classmethod
def load_builtins_and_core(cls, database: hammer_config.HammerDatabase) -> None:
"""
Helper function that loads builtins and core into a HammerDatabase.
"""
# Load in builtins.
builtins_path = os.path.join(cls.hammer_vlsi_path, "builtins.yml")
if not os.path.exists(builtins_path):
raise FileNotFoundError(
"hammer-vlsi builtin settings not found. Did you call HammerVLSISettings.set_hammer_vlsi_path_from_environment()?")
database.update_builtins([
hammer_config.load_config_from_file(builtins_path, strict=True),
HammerVLSISettings.get_config()
])
# Read in core defaults.
database.update_core(hammer_config.load_config_from_defaults(cls.hammer_vlsi_path, strict=True))
from .hammer_tool import HammerTool, HammerToolStep
class DummyHammerTool(HammerTool):
"""
This is a dummy implementation of HammerTool that does nothing.
It has no config, and no particular sense of versioning.
It is present for nop tools and as a testing aid.
"""
def tool_config_prefix(self) -> str:
return ""
def version_number(self, version: str) -> int:
return 1
@property
def steps(self) -> List[HammerToolStep]:
return []
class HammerSRAMGeneratorTool(HammerTool):
### Generated interface HammerSRAMGeneratorTool ###
### DO NOT MODIFY THIS CODE, EDIT generate_properties.py INSTEAD ###
### Inputs ###
@property
def input_parameters(self) -> List[SRAMParameters]:
"""
Get the input sram parameters to be generated.
:return: The input sram parameters to be generated.
"""
try:
return self.attr_getter("_input_parameters", None)
except AttributeError:
raise ValueError("Nothing set for the input sram parameters to be generated yet")
@input_parameters.setter
def input_parameters(self, value: List[SRAMParameters]) -> None:
"""Set the input sram parameters to be generated."""
if not (isinstance(value, List)):
raise TypeError("input_parameters must be a List[SRAMParameters]")
self.attr_setter("_input_parameters", value)
### Outputs ###
@property
def output_libraries(self) -> List[ExtraLibrary]:
"""
Get the list of the hammer tech libraries corresponding to generated srams.
:return: The list of the hammer tech libraries corresponding to generated srams.
"""
try:
return self.attr_getter("_output_libraries", None)
except AttributeError:
raise ValueError("Nothing set for the list of the hammer tech libraries corresponding to generated srams yet")
@output_libraries.setter
def output_libraries(self, value: List[ExtraLibrary]) -> None:
"""Set the list of the hammer tech libraries corresponding to generated srams."""
if not (isinstance(value, List)):
raise TypeError("output_libraries must be a List[ExtraLibrary]")
self.attr_setter("_output_libraries", value)
### END Generated interface HammerSRAMGeneratorTool ###
@property
def steps(self) -> List[HammerToolStep]:
steps = [
self.generate_all_srams_and_corners
]
return self.make_steps_from_methods(steps)
def fill_outputs(self) -> bool:
return True #we fill in output_libraries in generate_all_srams_and_corners
def export_config_outputs(self) -> Dict[str, Any]:
outputs = deepdict(super().export_config_outputs())
simple_ex = []
for ex in self.output_libraries: # type: ExtraLibrary
simple_lib = json.loads(ex.library.serialize())
if(ex.prefix == None):
new_ex = {"library": simple_lib}
else:
new_ex = {"prefix": ex.prefix, "library": simple_lib}
simple_ex.append(new_ex)
outputs["vlsi.technology.extra_libraries"] = simple_ex
outputs["vlsi.technology.extra_libraries_meta"] = "append"
return outputs
#TODO: Is this the right way for these two generate_all methods to work
# in techX16 you can generate only ever generate a single SRAM per run but can
# generate multiple corners at once
def generate_all_srams_and_corners(self) -> bool:
srams = reduce(list.__add__, list(map(lambda c: self.generate_all_srams(c), self.get_mmmc_corners()))) # type: List[ExtraLibrary]
self.output_libraries = srams
return True
def generate_all_srams(self, corner: MMMCCorner) -> List[ExtraLibrary]:
srams = list(map(lambda p: self.generate_sram(p, corner), self.input_parameters)) # type: List[ExtraLibrary]
return srams
# Run compiler for a single sram and corner
@abstractmethod
def generate_sram(self, params: SRAMParameters, corner: MMMCCorner) -> ExtraLibrary:
pass
class HammerSynthesisTool(HammerTool):
@abstractmethod
def fill_outputs(self) -> bool:
pass
def export_config_outputs(self) -> Dict[str, Any]:
outputs = deepdict(super().export_config_outputs())
outputs["synthesis.outputs.output_files"] = self.output_files
outputs["synthesis.inputs.input_files"] = self.input_files
outputs["synthesis.inputs.top_module"] = self.top_module
return outputs
### Generated interface HammerSynthesisTool ###
### DO NOT MODIFY THIS CODE, EDIT generate_properties.py INSTEAD ###
### Inputs ###
@property
def input_files(self) -> List[str]:
"""
Get the input collection of source RTL files (e.g. *.v).
:return: The input collection of source RTL files (e.g. *.v).
"""
try:
return self.attr_getter("_input_files", None)
except AttributeError:
raise ValueError("Nothing set for the input collection of source RTL files (e.g. *.v) yet")
@input_files.setter
def input_files(self, value: List[str]) -> None:
"""Set the input collection of source RTL files (e.g. *.v)."""
if not (isinstance(value, List)):
raise TypeError("input_files must be a List[str]")
self.attr_setter("_input_files", value)
### Outputs ###
@property
def output_files(self) -> List[str]:
"""
Get the output collection of mapped (post-synthesis) RTL files.
:return: The output collection of mapped (post-synthesis) RTL files.
"""
try:
return self.attr_getter("_output_files", None)
except AttributeError:
raise ValueError("Nothing set for the output collection of mapped (post-synthesis) RTL files yet")
@output_files.setter
def output_files(self, value: List[str]) -> None:
"""Set the output collection of mapped (post-synthesis) RTL files."""
if not (isinstance(value, List)):
raise TypeError("output_files must be a List[str]")
self.attr_setter("_output_files", value)
@property
def output_sdc(self) -> str:
"""
Get the (optional) output post-synthesis SDC constraints file.
:return: The (optional) output post-synthesis SDC constraints file.
"""
try:
return self.attr_getter("_output_sdc", None)
except AttributeError:
raise ValueError("Nothing set for the (optional) output post-synthesis SDC constraints file yet")
@output_sdc.setter
def output_sdc(self, value: str) -> None:
"""Set the (optional) output post-synthesis SDC constraints file."""
if not (isinstance(value, str)):
raise TypeError("output_sdc must be a str")
self.attr_setter("_output_sdc", value)
### END Generated interface HammerSynthesisTool ###
### Generated interface HammerSynthesisTool ###
class HammerPlaceAndRouteTool(HammerTool):
@abstractmethod
def fill_outputs(self) -> bool:
pass
def export_config_outputs(self) -> Dict[str, Any]:
outputs = deepdict(super().export_config_outputs())
outputs["par.outputs.output_ilms"] = list(map(lambda s: s.to_setting(), self.output_ilms))
outputs["par.outputs.output_ilms_meta"] = "append"
outputs["par.outputs.output_gds"] = str(self.output_gds)
outputs["par.outputs.output_netlist"] = str(self.output_netlist)
outputs["par.outputs.hcells_list"] = list(self.hcells_list)
return outputs
### Generated interface HammerPlaceAndRouteTool ###
### DO NOT MODIFY THIS CODE, EDIT generate_properties.py INSTEAD ###
### Inputs ###
@property
def input_files(self) -> List[str]:
"""
Get the input post-synthesis netlist files.
:return: The input post-synthesis netlist files.
"""
try:
return self.attr_getter("_input_files", None)
except AttributeError:
raise ValueError("Nothing set for the input post-synthesis netlist files yet")
@input_files.setter
def input_files(self, value: List[str]) -> None:
"""Set the input post-synthesis netlist files."""
if not (isinstance(value, List)):
raise TypeError("input_files must be a List[str]")
self.attr_setter("_input_files", value)
@property
def post_synth_sdc(self) -> Optional[str]:
"""
Get the (optional) input post-synthesis SDC constraint file.
:return: The (optional) input post-synthesis SDC constraint file.
"""
try:
return self.attr_getter("_post_synth_sdc", None)
except AttributeError:
return None
@post_synth_sdc.setter
def post_synth_sdc(self, value: Optional[str]) -> None:
"""Set the (optional) input post-synthesis SDC constraint file."""
if not (isinstance(value, str) or (value is None)):
raise TypeError("post_synth_sdc must be a Optional[str]")
self.attr_setter("_post_synth_sdc", value)
### Outputs ###
@property
def output_ilms(self) -> List[ILMStruct]:
"""
Get the (optional) output ILM information for hierarchical mode.
:return: The (optional) output ILM information for hierarchical mode.
"""
try:
return self.attr_getter("_output_ilms", None)
except AttributeError:
raise ValueError("Nothing set for the (optional) output ILM information for hierarchical mode yet")
@output_ilms.setter
def output_ilms(self, value: List[ILMStruct]) -> None:
"""Set the (optional) output ILM information for hierarchical mode."""
if not (isinstance(value, List)):
raise TypeError("output_ilms must be a List[ILMStruct]")
self.attr_setter("_output_ilms", value)
@property
def output_gds(self) -> str:
"""
Get the path to the output GDS file.
:return: The path to the output GDS file.
"""
try:
return self.attr_getter("_output_gds", None)
except AttributeError:
raise ValueError("Nothing set for the path to the output GDS file yet")
@output_gds.setter
def output_gds(self, value: str) -> None:
"""Set the path to the output GDS file."""
if not (isinstance(value, str)):
raise TypeError("output_gds must be a str")
self.attr_setter("_output_gds", value)
@property
def output_netlist(self) -> str:
"""
Get the path to the output netlist file.
:return: The path to the output netlist file.
"""
try:
return self.attr_getter("_output_netlist", None)
except AttributeError:
raise ValueError("Nothing set for the path to the output netlist file yet")
@output_netlist.setter
def output_netlist(self, value: str) -> None:
"""Set the path to the output netlist file."""
if not (isinstance(value, str)):
raise TypeError("output_netlist must be a str")
self.attr_setter("_output_netlist", value)
@property
def hcells_list(self) -> List[str]:
"""
Get the list of cells to explicitly map hierarchically in LVS.
:return: The list of cells to explicitly map hierarchically in LVS.
"""
try:
return self.attr_getter("_hcells_list", None)
except AttributeError:
raise ValueError("Nothing set for the list of cells to explicitly map hierarchically in LVS yet")
@hcells_list.setter
def hcells_list(self, value: List[str]) -> None:
"""Set the list of cells to explicitly map hierarchically in LVS."""
if not (isinstance(value, List)):
raise TypeError("hcells_list must be a List[str]")
self.attr_setter("_hcells_list", value)
### END Generated interface HammerPlaceAndRouteTool ###
def create_power_straps_tcl(self) -> List[str]:
"""
Create power straps TCL commands depending on the mode.
"""
output = [] # type: List[str]
power_straps_mode = str(self.get_setting("par.power_straps_mode"))
if power_straps_mode == "manual":
power_straps_script_contents = str(self.get_setting("par.power_straps_script_contents"))
# TODO(edwardw): proper source locators/SourceInfo
output.append("# Power straps script manually specified from HAMMER")
output.extend(power_straps_script_contents.split("\n"))
elif power_straps_mode == "generate":
output.extend(self.generate_power_straps_tcl())
else:
if power_straps_mode != "empty":
self.logger.error(
"Invalid power_straps_mode {mode}. Using blank power straps script.".format(mode=power_straps_mode))
# Write blank power straps
output.append("# Blank power straps script specified from HAMMER")
return output
def generate_power_straps_tcl(self) -> List[str]:
"""
Generate a TCL script to create power straps from the config/IR.
:return: Power straps TCL script.
"""
method = self.get_setting("par.generate_power_straps_method")
if method == "by_tracks":
# By default put straps everywhere
bbox = None # type: Optional[List[Decimal]]
namespace = "par.generate_power_straps_options.by_tracks"
layers = self.get_setting("{}.strap_layers".format(namespace))
pin_layers = self.get_setting("{}.pin_layers".format(namespace))
ground_net_names = list(map(lambda x: x.name, self.get_independent_ground_nets())) # type: List[str]
power_net_names = list(map(lambda x: x.name, self.get_independent_power_nets())) # type: List[str]
def get_weight(s: Supply) -> int:
# Check that it's not None
assert isinstance(s.weight, int)
return s.weight
weights = list(map(get_weight, self.get_independent_power_nets())) # type: List[int]
assert len(ground_net_names) == 1, "FIXME, I am assuming there's only 1 ground net"
return self.specify_all_power_straps_by_tracks(layers, ground_net_names[0], power_net_names, weights, bbox, pin_layers)
else:
raise NotImplementedError("Power strap generation method %s is not implemented" % method)
def specify_power_straps_by_tracks(self, layer_name: str, bottom_via_layer: str, blockage_spacing: Decimal, track_pitch: int, track_width: int, track_spacing: int, track_start: int, track_offset: Decimal, bbox: Optional[List[Decimal]], nets: List[str], add_pins: bool, layer_is_all_power: bool) -> List[str]:
"""
Generate a list of TCL commands that will create power straps on a given layer by specifying the desired track consumption.
This method assumes that power straps are built bottom-up, starting with standard cell rails.
:param layer_name: The layer name of the metal on which to create straps.
:param bottom_via_layer_name: The layer name of the lowest metal layer down to which to drop vias.
:param blockage_spacing: The minimum spacing between the end of a strap and the beginning of a macro or blockage.
:param track_pitch: The integer pitch between groups of power straps (i.e. from left edge of strap A to the next left edge of strap A) in units of the routing pitch.
:param track_width: The desired number of routing tracks to consume by a single power strap.
:param track_spacing: The desired number of USABLE routing tracks between power straps. It is recommended to leave this at 0 except to fix DRC issues.
:param track_start: The index of the first track to start using for power straps relative to the bounding box.
:param bbox: The optional (2N)-point bounding box of the area to generate straps. By default the entire core area is used.
:param nets: A list of power nets to create (e.g. ["VDD", "VSS"], ["VDDA", "VSS", "VDDB"], ... etc.).
:param add_pins: True if pins are desired on this layer; False otherwise.
:param layer_is_all_power: True if there will be no signal wires on this layer.
:return: A list of TCL commands that will generate power straps.
"""
# Note: even track_widths will be snapped to a half-track
layer = self.get_stackup().get_metal(layer_name)
pitch = track_pitch * layer.pitch
width = Decimal(0)
spacing = Decimal(0)
strap_offset = Decimal(0)
if track_spacing == 0:
# An all-power (100% utilization) layer results in us wanting to do a uniform strap pattern, so we can just calculate the
# maximum width and minimum spacing from the desired pitch, instead of using TWWT.
if layer_is_all_power:
one_strap_pitch = track_width * layer.pitch
spacing, width = layer.min_spacing_and_max_width_from_pitch(one_strap_pitch)
strap_start = spacing / 2 + layer.offset
else:
width, spacing, strap_start = layer.get_width_spacing_start_twwt(track_width, force_even=True)
else:
width, spacing, strap_start = layer.get_width_spacing_start_twt(track_width)
spacing = 2*spacing + (track_spacing - 1) * layer.pitch + layer.min_width
offset = track_offset + track_start * layer.pitch + strap_start
assert width > Decimal(0), "Width must be greater than zero. You probably have a malformed tech plugin on layer {}.".format(layer_name)
assert spacing > Decimal(0), "Spacing must be greater than zero. You probably have a malformed tech plugin on layer {}.".format(layer_name)
return self.specify_power_straps(layer_name, bottom_via_layer, blockage_spacing, pitch, width, spacing, offset, bbox, nets, add_pins)
def specify_all_power_straps_by_tracks(self, layer_names: List[str], ground_net: str, power_nets: List[str], power_weights: List[int], bbox: Optional[List[Decimal]], pin_layers: List[str]) -> List[str]:
"""
Generate a list of TCL commands that will create power straps on a given set of layers by specifying the desired per-track track consumption and utilization.
This will build standard cell power strap rails first. Layer-specific parameters are read from the hammer config:
- par.generate_power_straps_options.by_tracks.blockage_spacing
- par.generate_power_straps_options.by_tracks.track_width
- par.generate_power_straps_options.by_tracks.track_spacing
- par.generate_power_straps_options.by_tracks.power_utilization
These settings are all overridable by appending an underscore followed by the metal name (e.g. power_utilization_M3).
:param layer_names: The list of metal layer names on which to create straps.
:param ground_net: The name of the ground net in this design. Only 1 ground net is supported.
:param power_nets: A list of power nets to create (not ground).
:param power_weights: Specifies the power strap placement pattern for multiple-domain designs (e.g. ["VDDA", "VDDB"] with [2, 1] will produce 2 VDDA straps for ever 1 VDDB strap).
:param bbox: The optional (2N)-point bounding box of the area to generate straps. By default the entire core area is used.
:param pin_layers: A list of layers on which to place pins
:return: A list of TCL commands that will generate power straps.
"""
assert len(power_nets) == len(power_weights)
# Do some sanity checking
for l in pin_layers:
assert l in layer_names, "Pin layer {} must be in power strap layers".format(l)
# TODO does the CPF help this, or do we need to be more explicit about the bbox for each domain
output = self.specify_std_cell_power_straps(bbox, [ground_net] + power_nets)
bottom_via_layer = self.get_setting("technology.core.std_cell_rail_layer")
last = self.get_stackup().get_metal(bottom_via_layer)
for layer_name in layer_names:
layer = self.get_stackup().get_metal(layer_name)
assert layer.index > last.index, "Must build power straps bottom-up"
if last.direction == layer.direction:
raise ValueError("Layers {a} and {b} run in the same direction, but have no power straps between them.".format(a=last.name, b=layer.name))
blockage_spacing = coerce_to_grid(float(self._get_by_tracks_metal_setting("blockage_spacing", layer_name)), layer.grid_unit)
track_width = int(self._get_by_tracks_metal_setting("track_width", layer_name))
track_spacing = int(self._get_by_tracks_metal_setting("track_spacing", layer_name))
track_start = int(self._get_by_tracks_metal_setting("track_start", layer_name))
track_pitch = self._get_by_tracks_track_pitch(layer_name)
offset = layer.offset # TODO this is relaxable if we can auto-recalculate this based on hierarchical setting
add_pins = layer_name in pin_layers
# For multiple domains, we'll stripe them like this:
# 2:1 : A A B A A B ...
# 3:1 : A A A B A A A B ...
# 3:2 : A A A B B A A A B B ...
# 2:2:1 : A A B B C A A B B C ...
sum_weights = sum(power_weights)
# If the power + ground tracks are equal to the pitch, we have no signals
layer_is_all_power = (2 * track_width) == track_pitch
for i in range(sum_weights):
nets = [ground_net, power_nets[i]]
group_offset = offset + track_pitch * i * layer.pitch
group_pitch = sum_weights * track_pitch
output.extend(self.specify_power_straps_by_tracks(layer_name, last.name, blockage_spacing, group_pitch, track_width, track_spacing, track_start, group_offset, bbox, nets, add_pins, layer_is_all_power))
last = layer
return output
_power_straps_last_index = -1
def _power_straps_check_index(self, layer_name: str) -> None:
next_index = self.get_stackup().get_metal(layer_name).index
assert next_index >= self._power_straps_last_index, "Must construct power straps from bottom to top"
self._power_straps_last_index = next_index
def _get_by_tracks_metal_setting(self, key: str, layer_name: str) -> Any:
"""
Return the metal setting used by the by_tracks power strap generation method.
This will return the value from the provided key in the par.generate_power_straps.by_tracks namespace,
which can be overridden for a specific metal layer by appending _<layer name>.
:param key: The base key name (e.g. track_spacing). Do not include the namespace or metal override.
:return: The value associated with the key, after applying any metal overrides
"""
default = "par.generate_power_straps_options.by_tracks." + key
override = default + "_" + layer_name
try:
return self.get_setting(override)
except KeyError:
try:
return self.get_setting(default)
except KeyError:
raise ValueError("No value set for key {}".format(default))
def _get_by_tracks_track_pitch(self, layer_name: str) -> int:
"""
Returns the track pitch used by the by_tracks power rail generation method
:param layer_name: The string name of the metal layer
:return: The power strap group pitch in tracks
"""
track_width = int(self._get_by_tracks_metal_setting("track_width", layer_name))
track_spacing = int(self._get_by_tracks_metal_setting("track_spacing", layer_name))
power_utilization = float(self._get_by_tracks_metal_setting("power_utilization", layer_name))
assert power_utilization > 0.0
assert power_utilization <= 1.0
# Calculate how many tracks we consume
# This strategy uses pairs of power and ground
consumed_tracks = 2 * track_width + track_spacing
return round(consumed_tracks / power_utilization)
@abstractmethod
def specify_power_straps(self, layer_name: str, bottom_via_layer_name: str, blockage_spacing: Decimal, pitch: Decimal, width: Decimal, spacing: Decimal, offset: Decimal, bbox: Optional[List[Decimal]], nets: List[str], add_pins: bool) -> List[str]:
"""
Generate a list of TCL commands that will create power straps on a given layer.
This is a low-level, cad-tool-specific API. It is designed to be called by higher-level methods, so calling this directly is not recommended.
This method assumes that power straps are built bottom-up, starting with standard cell rails.
:param layer_name: The layer name of the metal on which to create straps.
:param bottom_via_layer_name: The layer name of the lowest metal layer down to which to drop vias.
:param blockage_spacing: The minimum spacing between the end of a strap and the beginning of a macro or blockage.
:param pitch: The pitch between groups of power straps (i.e. from left edge of strap A to the next left edge of strap A).
:param width: The width of each strap in a group.
:param spacing: The spacing between straps in a group.
:param offset: The offset to start the first group.
:param bbox: The optional (2N)-point bounding box of the area to generate straps. By default the entire core area is used.
:param nets: A list of power nets to create (e.g. ["VDD", "VSS"], ["VDDA", "VSS", "VDDB"], ... etc.).
:param add_pins: True if pins are desired on this layer; False otherwise.
:return: A list of TCL commands that will generate power straps.
"""
# This should get overriden but be sure to use this check in your implementations
self._power_straps_check_index(layer_name)
return []
@abstractmethod
def specify_std_cell_power_straps(self, bbox: Optional[List[Decimal]], nets: List[str]) -> List[str]:
"""
Generate a list of TCL commands that build the low-level standard cell power strap rails.
This is a low-level, cad-tool-specific API. It is designed to be called by higher-level methods, so calling this directly is not recommended.
This will create power straps based on technology.core.tap_cell_rail_reference.
The layer is set by technology.core.std_cell_rail_layer, which should be the highest metal layer in the std cell rails.
This method should be called before any calls to specify_power_straps.
:param bbox: The optional (2N)-point bounding box of the area to generate straps. By default the entire core area is used.
:param nets: A list of power net names (e.g. ["VDD", "VSS"]).
:return: A list of TCL commands that will generate power straps on rails.
"""
# This should get overriden but be sure to use this check in your implementations
layer_name = self.get_setting("technology.core.std_cell_rail_layer")
self._power_straps_check_index(layer_name)
return []
class HammerSignoffTool(HammerTool):
@abstractmethod
def fill_outputs(self) -> bool:
pass
### Inputs ###
### Outputs ###
@abstractmethod
def signoff_results(self) -> int:
"""
Return the number of issues raised by the signoff tool (0 = all checks pass).
Individual tools extending HammerSignoffTool should implement their own *_results methods that provide tool-specific information,
and then pass a meaningful count of issues to their implementation of this method.
:return: The number of signoff issues raised by the tool
"""
pass
class HammerDRCTool(HammerSignoffTool):
@abstractmethod
def fill_outputs(self) -> bool:
pass
@abstractmethod
def globally_waived_drc_rules(self) -> List[str]:
# TODO(johnwright) how to waive specific instances of DRC rules, rather than blanket waivers
# TODO(johnwright) should this go in the YAML file?
"""
Get the list of waived DRC rule names.
:return: The list of waived DRC rule names.
"""
pass
def get_additional_drc_text(self) -> str:
""" Get the additional custom DRC command text to add after the boilerplate commands at the top of the DRC run file. """
# Mode can be auto, manual, append, or prepend
add_drc_text_mode = str(self.get_setting("drc.inputs.additional_drc_text_mode"))
# manul_add_drc_text will only be used in manual, append, and prepend modes
manual_add_drc_text = str(self.get_setting("drc.inputs.additional_drc_text"))
# tech_add_drc_text will only be used in auto, append, and prepend modes
tech_add_drc_text = get_or_else(self.technology.additional_drc_text, "") # type: str
# Default to auto (use tech_add_drc_text)
add_drc_text = tech_add_drc_text
if add_drc_text_mode == "auto":
pass
elif add_drc_text_mode == "manual":
add_drc_text = manual_add_drc_text
elif add_drc_text_mode == "append":
add_drc_text = tech_add_drc_text + manual_add_drc_text
elif add_drc_text_mode == "prepend":
add_drc_text = manual_add_drc_text + tech_add_drc_text
else:
self.logger.error(
"Invalid additional_drc_text_mode {mode}. Using auto.".format(mode=add_drc_text_mode))
return add_drc_text
@abstractmethod
def drc_results_pre_waived(self) -> Dict[str, int]:
""" Return a Dict mapping the DRC check name to an error count (pre-waivers). """
pass
def signoff_results(self) -> int:
""" Return the count of unwaived DRC errors. """
return sum(self.drc_results().values())
def drc_results(self) -> Dict[str, int]:
""" Return a Dict mapping the DRC check name to an error count (with waivers). """
res = self.drc_results_pre_waived()
return {k: 0 if k in self.globally_waived_drc_rules() else int(res[k]) for k in res}
### Generated interface HammerDRCTool ###
### DO NOT MODIFY THIS CODE, EDIT generate_properties.py INSTEAD ###
### Inputs ###
@property
def layout_file(self) -> str:
"""
Get the path to the input layout file (e.g. a *.gds).
:return: The path to the input layout file (e.g. a *.gds).
"""
try:
return self.attr_getter("_layout_file", None)
except AttributeError:
raise ValueError("Nothing set for the path to the input layout file (e.g. a *.gds) yet")
@layout_file.setter
def layout_file(self, value: str) -> None:
"""Set the path to the input layout file (e.g. a *.gds)."""
if not (isinstance(value, str)):
raise TypeError("layout_file must be a str")
self.attr_setter("_layout_file", value)
### Outputs ###
### END Generated interface HammerDRCTool ###
class HammerLVSTool(HammerSignoffTool):
@abstractmethod
def fill_outputs(self) -> bool:
pass
@abstractmethod
def globally_waived_erc_rules(self) -> List[str]:
# TODO(johnwright) how to waive specific instances of ERC rules, rather than blanket waivers
# TODO(johnwright) should this go in the YAML file?
"""
Get the list of waived ERC rule names.
:return: The list of waived ERC rule names.
"""
pass
@abstractmethod
def erc_results_pre_waived(self) -> Dict[str, int]:
""" Return a Dict mapping the ERC check name to an error count (pre-waivers). """
pass
def signoff_results(self) -> int:
""" Return the count of unwaived ERC errors and LVS errors. """
return sum(self.erc_results().values()) + len(self.lvs_results())
def erc_results(self) -> Dict[str, int]:
""" Return a Dict mapping the ERC check name to an error count (with waivers). """
res = self.erc_results_pre_waived()
return {k: 0 if k in self.globally_waived_erc_rules() else int(res[k]) for k in res}
@abstractmethod
def lvs_results(self) -> List[str]:
""" Return the LVS issue descriptions for each issue. An empty list means LVS passes. """
pass
def get_additional_lvs_text(self) -> str:
""" Get the additional custom LVS command text to add after the boilerplate commands at the top of the LVS run file. """
# Mode can be auto, manual, append, or prepend
add_lvs_text_mode = str(self.get_setting("lvs.inputs.additional_lvs_text_mode"))
# manul_add_lvs_text will only be used in manual, append, and prepend modes
manual_add_lvs_text = str(self.get_setting("lvs.inputs.additional_lvs_text"))
# tech_add_lvs_text will only be used in auto, append, and prepend modes
tech_add_lvs_text = get_or_else(self.technology.additional_lvs_text, "") # type: str
# Default to auto (use tech_add_lvs_text)
add_lvs_text = tech_add_lvs_text
if add_lvs_text_mode == "auto":
pass
elif add_lvs_text_mode == "manual":
add_lvs_text = manual_add_lvs_text
elif add_lvs_text_mode == "append":
add_lvs_text = tech_add_lvs_text + manual_add_lvs_text
elif add_lvs_text_mode == "prepend":
add_lvs_text = manual_add_lvs_text + tech_add_lvs_text
else:
self.logger.error(
"Invalid additional_lvs_text_mode {mode}. Using auto.".format(mode=add_lvs_text_mode))
return add_lvs_text
### Generated interface HammerLVSTool ###
### DO NOT MODIFY THIS CODE, EDIT generate_properties.py INSTEAD ###
### Inputs ###
@property
def layout_file(self) -> str:
"""
Get the path to the input layout file (e.g. a *.gds).
:return: The path to the input layout file (e.g. a *.gds).
"""
try:
return self.attr_getter("_layout_file", None)
except AttributeError:
raise ValueError("Nothing set for the path to the input layout file (e.g. a *.gds) yet")
@layout_file.setter
def layout_file(self, value: str) -> None:
"""Set the path to the input layout file (e.g. a *.gds)."""
if not (isinstance(value, str)):
raise TypeError("layout_file must be a str")
self.attr_setter("_layout_file", value)
@property
def schematic_files(self) -> List[str]:
"""
Get the path to the input SPICE or Verilog schematic files (e.g. *.v or *.spi).
:return: The path to the input SPICE or Verilog schematic files (e.g. *.v or *.spi).
"""
try:
return self.attr_getter("_schematic_files", None)
except AttributeError:
raise ValueError("Nothing set for the path to the input SPICE or Verilog schematic files (e.g. *.v or *.spi) yet")
@schematic_files.setter
def schematic_files(self, value: List[str]) -> None:
"""Set the path to the input SPICE or Verilog schematic files (e.g. *.v or *.spi)."""
if not (isinstance(value, List)):
raise TypeError("schematic_files must be a List[str]")
self.attr_setter("_schematic_files", value)
@property
def hcells_list(self) -> List[str]:
"""
Get the list of cells to explicitly map hierarchically in LVS.
:return: The list of cells to explicitly map hierarchically in LVS.
"""
try:
return self.attr_getter("_hcells_list", None)
except AttributeError:
raise ValueError("Nothing set for the list of cells to explicitly map hierarchically in LVS yet")
@hcells_list.setter
def hcells_list(self, value: List[str]) -> None:
"""Set the list of cells to explicitly map hierarchically in LVS."""
if not (isinstance(value, List)):
raise TypeError("hcells_list must be a List[str]")
self.attr_setter("_hcells_list", value)
@property
def ilms(self) -> List[ILMStruct]:
"""
Get the list of (optional) input ILM information for hierarchical mode.
:return: The list of (optional) input ILM information for hierarchical mode.
"""
try:
return self.attr_getter("_ilms", None)
except AttributeError:
raise ValueError("Nothing set for the list of (optional) input ILM information for hierarchical mode yet")
@ilms.setter
def ilms(self, value: List[ILMStruct]) -> None:
"""Set the list of (optional) input ILM information for hierarchical mode."""
if not (isinstance(value, List)):
raise TypeError("ilms must be a List[ILMStruct]")
self.attr_setter("_ilms", value)
### Outputs ###
### END Generated interface HammerLVSTool ###
class HasUPFSupport(HammerTool):
"""Mix-in trait with functions useful for tools with UPF style power
constraints"""
@property
def upf_power_specification(self) -> str:
raise NotImplementedError("Automatic generation of UPF power specifications is not supported yet.")
class HasCPFSupport(HammerTool):
"""Mix-in trait with functions useful for tools with CPF style power
constraints"""
@property
def cpf_power_specification(self) -> str:
output = [] # type: List[str]
# Just names
domain = "AO"
condition = "nominal"
mode = "aon"
# Header
output.append("set_cpf_version 1.0e")
output.append("set_hierarchy_separator /")
output.append("set_design {t}".format(t=self.top_module))
# Define power and ground nets
power_nets = self.get_all_power_nets() # type: List[Supply]
ground_nets = self.get_all_ground_nets() # type: List[Supply]
vdd = VoltageValue(self.get_setting("vlsi.inputs.supplies.VDD")) # type: VoltageValue
output.append("create_power_nets -nets {{ {p} }} -voltage {v}".
format(p=" ".join(map(lambda x: x.name, power_nets)), v=vdd.value))
output.append("create_ground_nets -nets {{ {g} }}".
format(g=" ".join(map(lambda x: x.name, ground_nets))))
# Define power domain and connections
output.append("create_power_domain -name {d} -default".format(d=domain))
# Assume primary power are first in list
output.append("update_power_domain -name {d} -primary_power_net {pp} -primary_ground_net {pg}".
format(d=domain, pp=power_nets[0].name, pg=ground_nets[0].name))
# Assuming that all power/ground nets correspond to pins
for pg_net in (power_nets+ground_nets):
if(pg_net.pin != None):
output.append("create_global_connection -domain {d} -net {n} -pins {p}".
format(d=domain, n=pg_net.name, p=pg_net.pin))
# Create nominal operation condtion and power mode
output.append("create_nominal_condition -name {c} -voltage {v}".
format(c=condition, v=vdd.value))
output.append("create_power_mode -name {m} -default -domain_conditions {{{d}@{c}}}".
format(m=mode, d=domain, c=condition))
# Footer
output.append("end_design")
return "\n".join(output)
class HasSDCSupport(HammerTool):
"""Mix-in trait with functions useful for tools with SDC-style
constraints."""
@property
def sdc_clock_constraints(self) -> str:
"""Generate TCL fragments for top module clock constraints."""
output = [] # type: List[str]
clocks = self.get_clock_ports()
for clock in clocks: