librelane 2.4.0.dev0__py3-none-any.whl → 2.4.0.dev3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of librelane might be problematic. Click here for more details.
- librelane/__main__.py +6 -1
- librelane/config/config.py +2 -2
- librelane/config/variable.py +12 -7
- librelane/container.py +1 -1
- librelane/env_info.py +1 -1
- librelane/flows/classic.py +1 -4
- librelane/flows/optimizing.py +1 -1
- librelane/scripts/odbpy/defutil.py +6 -7
- librelane/scripts/odbpy/eco_buffer.py +181 -0
- librelane/scripts/odbpy/eco_diode.py +139 -0
- librelane/scripts/odbpy/{exception_codes.py → ioplace_parser/__init__.py} +11 -5
- librelane/scripts/odbpy/ioplace_parser/parse.py +147 -0
- librelane/scripts/odbpy/reader.py +15 -11
- librelane/scripts/openroad/common/io.tcl +66 -2
- librelane/steps/__init__.py +1 -1
- librelane/steps/__main__.py +1 -1
- librelane/steps/odb.py +160 -24
- librelane/steps/openroad.py +7 -2
- {librelane-2.4.0.dev0.dist-info → librelane-2.4.0.dev3.dist-info}/METADATA +28 -9
- {librelane-2.4.0.dev0.dist-info → librelane-2.4.0.dev3.dist-info}/RECORD +22 -19
- {librelane-2.4.0.dev0.dist-info → librelane-2.4.0.dev3.dist-info}/WHEEL +1 -1
- {librelane-2.4.0.dev0.dist-info → librelane-2.4.0.dev3.dist-info}/entry_points.txt +0 -0
|
@@ -131,16 +131,9 @@ class OdbReader(object):
|
|
|
131
131
|
dpl.reportLegalizationStats()
|
|
132
132
|
dpl.optimizeMirroring()
|
|
133
133
|
|
|
134
|
-
def
|
|
135
|
-
""
|
|
136
|
-
The ``._grt()`` method is EXPERIMENTAL and SHOULD NOT BE USED YET.
|
|
137
|
-
|
|
138
|
-
Use a composite step with ``OpenROAD.GlobalRouting``.
|
|
139
|
-
"""
|
|
140
|
-
if self.config is None:
|
|
141
|
-
raise RuntimeError("Attempted to call grt without config file")
|
|
134
|
+
def _grt_setup(self, grt):
|
|
135
|
+
grt.setAdjustment(float(self.config["GRT_ADJUSTMENT"]))
|
|
142
136
|
|
|
143
|
-
grt = self.design.getGlobalRouter()
|
|
144
137
|
routing_layers = [l for l in self.layers.values() if l.getRoutingLevel() >= 1]
|
|
145
138
|
for layer, adj in zip(routing_layers, self.config["GRT_LAYER_ADJUSTMENTS"]):
|
|
146
139
|
grt.addLayerAdjustment(
|
|
@@ -170,14 +163,25 @@ class OdbReader(object):
|
|
|
170
163
|
raise RuntimeError(f"Unknown layer name '{max_clk_name}'")
|
|
171
164
|
max_clk_idx = self.layers[max_clk_name].getRoutingLevel()
|
|
172
165
|
|
|
173
|
-
grt.setMinRoutingLayer(min_layer_idx)
|
|
174
|
-
grt.setMaxRoutingLayer(max_layer_idx)
|
|
175
166
|
grt.setMinLayerForClock(min_clk_idx)
|
|
176
167
|
grt.setMaxLayerForClock(max_clk_idx)
|
|
177
168
|
grt.setMacroExtension(self.config["GRT_MACRO_EXTENSION"])
|
|
178
169
|
grt.setOverflowIterations(self.config["GRT_OVERFLOW_ITERS"])
|
|
179
170
|
grt.setAllowCongestion(self.config["GRT_ALLOW_CONGESTION"])
|
|
180
171
|
grt.setVerbose(True)
|
|
172
|
+
grt.initFastRoute(min_layer_idx, max_layer_idx)
|
|
173
|
+
|
|
174
|
+
def _grt(self):
|
|
175
|
+
"""
|
|
176
|
+
The ``._grt()`` method is EXPERIMENTAL and SHOULD NOT BE USED YET.
|
|
177
|
+
|
|
178
|
+
Use a composite step with ``OpenROAD.GlobalRouting``.
|
|
179
|
+
"""
|
|
180
|
+
if self.config is None:
|
|
181
|
+
raise RuntimeError("Attempted to call grt without config file")
|
|
182
|
+
|
|
183
|
+
grt = self.design.getGlobalRouter()
|
|
184
|
+
self._grt_setup(grt)
|
|
181
185
|
grt.globalRoute(
|
|
182
186
|
True
|
|
183
187
|
) # The first variable updates guides- not sure why the default is False
|
|
@@ -316,6 +316,70 @@ proc read_current_odb {args} {
|
|
|
316
316
|
set_dont_use_cells
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
+
proc _populate_cells_by_class {} {
|
|
320
|
+
if { [info exists ::_cells_by_class(physical)] } {
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
set ::_cells_by_class(physical) [list]
|
|
325
|
+
set ::_cells_by_class(non_timing) [list]
|
|
326
|
+
set _comment_ {
|
|
327
|
+
We naïvely assume anything not in these classes is not a cell with a
|
|
328
|
+
logical function. This may not be comprehensive, but is good enough.
|
|
329
|
+
|
|
330
|
+
CORE just means a macro used in the core area (i.e. a standard cell.)
|
|
331
|
+
|
|
332
|
+
Thing is, it has a lot of subclasses for physical cells:
|
|
333
|
+
|
|
334
|
+
`FEEDTHRU`,`SPACER`,`ANTENNACELL`,`WELLTAP`
|
|
335
|
+
|
|
336
|
+
Only `TIEHIGH`, `TIELOW` are for logical cells. Thus, the inclusion
|
|
337
|
+
list allows them as well. `BLOCKS` are macros, which we cannot discern
|
|
338
|
+
whether they have a logical function or not, so we include them
|
|
339
|
+
regardless.
|
|
340
|
+
|
|
341
|
+
We do make one exception for `ANTENNACELL`s. These are not counted as
|
|
342
|
+
logical cells but they are not exempt from the so-called SDF-friendly
|
|
343
|
+
netlist as they do affect timing ever so slightly.
|
|
344
|
+
}
|
|
345
|
+
set logical_classes {
|
|
346
|
+
BLOCK
|
|
347
|
+
BUMP
|
|
348
|
+
CORE
|
|
349
|
+
CORE_TIEHIGH
|
|
350
|
+
CORE_TIELOW
|
|
351
|
+
COVER
|
|
352
|
+
PAD
|
|
353
|
+
PAD_AREAIO
|
|
354
|
+
PAD_INOUT
|
|
355
|
+
PAD_INPUT
|
|
356
|
+
PAD_OUTPUT
|
|
357
|
+
PAD_POWER
|
|
358
|
+
PAD_SPACER
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
foreach lib $::libs {
|
|
362
|
+
foreach master [$lib getMasters] {
|
|
363
|
+
if { [lsearch -exact $logical_classes [$master getType]] == -1 } {
|
|
364
|
+
lappend ::_cells_by_class(physical) [$master getName]
|
|
365
|
+
if { "[$master getType]" != "CORE_ANTENNACELL" } {
|
|
366
|
+
lappend ::_cells_by_class(non_timing) [$master getName]
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
proc get_timing_excluded_cells {args} {
|
|
374
|
+
_populate_cells_by_class
|
|
375
|
+
return $::_cells_by_class(non_timing)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
proc get_physical_cells {args} {
|
|
379
|
+
_populate_cells_by_class
|
|
380
|
+
return $::_cells_by_class(physical)
|
|
381
|
+
}
|
|
382
|
+
|
|
319
383
|
proc write_views {args} {
|
|
320
384
|
# This script will attempt to write views based on existing "SAVE_"
|
|
321
385
|
# environment variables. If the SAVE_ variable exists, the script will
|
|
@@ -349,7 +413,7 @@ proc write_views {args} {
|
|
|
349
413
|
}
|
|
350
414
|
|
|
351
415
|
if { [info exists ::env(SAVE_POWERED_NETLIST_SDF_FRIENDLY)] } {
|
|
352
|
-
set exclude_cells "[
|
|
416
|
+
set exclude_cells "[get_timing_excluded_cells]"
|
|
353
417
|
puts "Writing nofill powered netlist to '$::env(SAVE_POWERED_NETLIST_SDF_FRIENDLY)'…"
|
|
354
418
|
puts "Excluding $exclude_cells"
|
|
355
419
|
write_verilog -include_pwr_gnd \
|
|
@@ -358,7 +422,7 @@ proc write_views {args} {
|
|
|
358
422
|
}
|
|
359
423
|
|
|
360
424
|
if { [info exists ::env(SAVE_POWERED_NETLIST_NO_PHYSICAL_CELLS)] } {
|
|
361
|
-
set exclude_cells "[
|
|
425
|
+
set exclude_cells "[get_physical_cells]"
|
|
362
426
|
puts "Writing nofilldiode powered netlist to '$::env(SAVE_POWERED_NETLIST_NO_PHYSICAL_CELLS)'…"
|
|
363
427
|
puts "Excluding $exclude_cells"
|
|
364
428
|
write_verilog -include_pwr_gnd \
|
librelane/steps/__init__.py
CHANGED
librelane/steps/__main__.py
CHANGED
|
@@ -418,7 +418,7 @@ def create_reproducible(
|
|
|
418
418
|
* The current working directory
|
|
419
419
|
|
|
420
420
|
These reproducibles are filesystem-independent, i.e. they can be run
|
|
421
|
-
on any computer that has the appropriate version of LibreLane
|
|
421
|
+
on any computer that has the appropriate version of LibreLane installed
|
|
422
422
|
(as well as the underlying utility for that specific step.)
|
|
423
423
|
|
|
424
424
|
The reproducible will report an error if LibreLane is not installed and will
|
librelane/steps/odb.py
CHANGED
|
@@ -19,28 +19,27 @@ from math import inf
|
|
|
19
19
|
from decimal import Decimal
|
|
20
20
|
from functools import reduce
|
|
21
21
|
from abc import abstractmethod
|
|
22
|
+
from dataclasses import dataclass
|
|
22
23
|
from typing import Dict, List, Literal, Optional, Tuple
|
|
23
24
|
|
|
25
|
+
from ..common import Path, get_script_dir, aggregate_metrics
|
|
26
|
+
from ..config import Instance, Macro, Variable
|
|
27
|
+
from ..logging import info, verbose
|
|
28
|
+
from ..state import DesignFormat, State
|
|
24
29
|
|
|
25
|
-
from .common_variables import io_layer_variables
|
|
26
|
-
from .openroad_alerts import (
|
|
27
|
-
OpenROADAlert,
|
|
28
|
-
OpenROADOutputProcessor,
|
|
29
|
-
)
|
|
30
30
|
from .openroad import DetailedPlacement, GlobalRouting
|
|
31
|
-
from .
|
|
31
|
+
from .openroad_alerts import OpenROADAlert, OpenROADOutputProcessor
|
|
32
|
+
from .common_variables import io_layer_variables, dpl_variables, grt_variables
|
|
32
33
|
from .step import (
|
|
33
|
-
|
|
34
|
+
CompositeStep,
|
|
35
|
+
DefaultOutputProcessor,
|
|
34
36
|
MetricsUpdate,
|
|
35
37
|
Step,
|
|
38
|
+
StepError,
|
|
36
39
|
StepException,
|
|
37
|
-
|
|
38
|
-
DefaultOutputProcessor,
|
|
40
|
+
ViewsUpdate,
|
|
39
41
|
)
|
|
40
|
-
from
|
|
41
|
-
from ..config import Variable, Macro, Instance
|
|
42
|
-
from ..state import State, DesignFormat
|
|
43
|
-
from ..common import Path, get_script_dir
|
|
42
|
+
from .tclstep import TclStep
|
|
44
43
|
|
|
45
44
|
inf_rx = re.compile(r"\b(-?)inf\b")
|
|
46
45
|
|
|
@@ -51,6 +50,8 @@ class OdbpyStep(Step):
|
|
|
51
50
|
|
|
52
51
|
output_processors = [OpenROADOutputProcessor, DefaultOutputProcessor]
|
|
53
52
|
|
|
53
|
+
alerts: Optional[List[OpenROADAlert]] = None
|
|
54
|
+
|
|
54
55
|
def on_alert(self, alert: OpenROADAlert) -> OpenROADAlert:
|
|
55
56
|
if alert.code in [
|
|
56
57
|
"ORD-0039", # .openroad ignored with -python
|
|
@@ -63,7 +64,9 @@ class OdbpyStep(Step):
|
|
|
63
64
|
self.warn(str(alert), extra={"key": alert.code})
|
|
64
65
|
return alert
|
|
65
66
|
|
|
66
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
67
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
68
|
+
self.alerts = None
|
|
69
|
+
|
|
67
70
|
kwargs, env = self.extract_env(kwargs)
|
|
68
71
|
|
|
69
72
|
automatic_outputs = set(self.outputs).intersection(
|
|
@@ -86,15 +89,35 @@ class OdbpyStep(Step):
|
|
|
86
89
|
env["PYTHONPATH"] = (
|
|
87
90
|
f'{os.path.join(get_script_dir(), "odbpy")}:{env.get("PYTHONPATH")}'
|
|
88
91
|
)
|
|
92
|
+
check = False
|
|
93
|
+
if "check" in kwargs:
|
|
94
|
+
check = kwargs.pop("check")
|
|
89
95
|
|
|
90
96
|
subprocess_result = self.run_subprocess(
|
|
91
97
|
command,
|
|
92
98
|
env=env,
|
|
99
|
+
check=check,
|
|
93
100
|
**kwargs,
|
|
94
101
|
)
|
|
102
|
+
generated_metrics = subprocess_result["generated_metrics"]
|
|
95
103
|
|
|
104
|
+
# 1. Parse warnings and errors
|
|
105
|
+
self.alerts = subprocess_result.get("openroad_alerts") or []
|
|
106
|
+
if subprocess_result["returncode"] != 0:
|
|
107
|
+
error_strings = [
|
|
108
|
+
str(alert) for alert in self.alerts if alert.cls == "error"
|
|
109
|
+
]
|
|
110
|
+
if len(error_strings):
|
|
111
|
+
error_string = "\n".join(error_strings)
|
|
112
|
+
raise StepError(
|
|
113
|
+
f"{self.id} failed with the following errors:\n{error_string}"
|
|
114
|
+
)
|
|
115
|
+
else:
|
|
116
|
+
raise StepException(
|
|
117
|
+
f"{self.id} failed unexpectedly. Please check the logs and file an issue."
|
|
118
|
+
)
|
|
119
|
+
# 2. Metrics
|
|
96
120
|
metrics_path = os.path.join(self.step_dir, "or_metrics_out.json")
|
|
97
|
-
metrics_updates: MetricsUpdate = subprocess_result["generated_metrics"]
|
|
98
121
|
if os.path.exists(metrics_path):
|
|
99
122
|
or_metrics_out = json.loads(open(metrics_path).read(), parse_float=Decimal)
|
|
100
123
|
for key, value in or_metrics_out.items():
|
|
@@ -102,9 +125,11 @@ class OdbpyStep(Step):
|
|
|
102
125
|
or_metrics_out[key] = inf
|
|
103
126
|
elif value == "-Infinity":
|
|
104
127
|
or_metrics_out[key] = -inf
|
|
105
|
-
|
|
128
|
+
generated_metrics.update(or_metrics_out)
|
|
106
129
|
|
|
107
|
-
|
|
130
|
+
metric_updates_with_aggregates = aggregate_metrics(generated_metrics)
|
|
131
|
+
|
|
132
|
+
return views_updates, metric_updates_with_aggregates
|
|
108
133
|
|
|
109
134
|
def get_command(self) -> List[str]:
|
|
110
135
|
metrics_path = os.path.join(self.step_dir, "or_metrics_out.json")
|
|
@@ -180,7 +205,7 @@ class CheckMacroAntennaProperties(OdbpyStep):
|
|
|
180
205
|
args += ["--cell-name", name]
|
|
181
206
|
return super().get_command() + args
|
|
182
207
|
|
|
183
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
208
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
184
209
|
if not self.get_cells():
|
|
185
210
|
info("No cells provided, skipping…")
|
|
186
211
|
return {}, {}
|
|
@@ -244,7 +269,7 @@ class ApplyDEFTemplate(OdbpyStep):
|
|
|
244
269
|
args.append("--copy-def-power")
|
|
245
270
|
return super().get_command() + args
|
|
246
271
|
|
|
247
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
272
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
248
273
|
if self.config["FP_DEF_TEMPLATE"] is None:
|
|
249
274
|
info("No DEF template provided, skipping…")
|
|
250
275
|
return {}, {}
|
|
@@ -339,7 +364,7 @@ class WriteVerilogHeader(OdbpyStep):
|
|
|
339
364
|
|
|
340
365
|
return command
|
|
341
366
|
|
|
342
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
367
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
343
368
|
views_updates, metrics_updates = super().run(state_in, **kwargs)
|
|
344
369
|
views_updates[DesignFormat.VERILOG_HEADER] = Path(
|
|
345
370
|
os.path.join(self.step_dir, f"{self.config['DESIGN_NAME']}.vh")
|
|
@@ -528,7 +553,7 @@ class AddRoutingObstructions(OdbpyStep):
|
|
|
528
553
|
command.append(obstruction)
|
|
529
554
|
return command
|
|
530
555
|
|
|
531
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
556
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
532
557
|
if self.config[self.get_obstruction_variable().name] is None:
|
|
533
558
|
info(
|
|
534
559
|
f"'{self.get_obstruction_variable().name}' is not defined. Skipping '{self.id}'…"
|
|
@@ -659,7 +684,7 @@ class CustomIOPlacement(OdbpyStep):
|
|
|
659
684
|
+ length_args
|
|
660
685
|
)
|
|
661
686
|
|
|
662
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
687
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
663
688
|
if self.config["FP_PIN_ORDER_CFG"] is None:
|
|
664
689
|
info("No custom floorplan file configured, skipping…")
|
|
665
690
|
return {}, {}
|
|
@@ -895,7 +920,7 @@ class CellFrequencyTables(OdbpyStep):
|
|
|
895
920
|
def get_buffer_list_script(self):
|
|
896
921
|
return os.path.join(get_script_dir(), "openroad", "buffer_list.tcl")
|
|
897
922
|
|
|
898
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
923
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
899
924
|
kwargs, env = self.extract_env(kwargs)
|
|
900
925
|
|
|
901
926
|
env_copy = env.copy()
|
|
@@ -948,8 +973,119 @@ class ManualGlobalPlacement(OdbpyStep):
|
|
|
948
973
|
assert self.config_path is not None, "get_command called before start()"
|
|
949
974
|
return super().get_command() + ["--step-config", self.config_path]
|
|
950
975
|
|
|
951
|
-
def run(self, state_in, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
976
|
+
def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
|
|
952
977
|
if self.config["MANUAL_GLOBAL_PLACEMENTS"] is None:
|
|
953
978
|
info("'MANUAL_GLOBAL_PLACEMENTS' not set, skipping…")
|
|
954
979
|
return {}, {}
|
|
955
980
|
return super().run(state_in, **kwargs)
|
|
981
|
+
|
|
982
|
+
|
|
983
|
+
@dataclass
|
|
984
|
+
class ECOBuffer:
|
|
985
|
+
"""
|
|
986
|
+
:param target: The driver to insert an ECO buffer after or sink to insert an
|
|
987
|
+
ECO buffer before, in the format instance_name/pin_name.
|
|
988
|
+
:param buffer: The kind of buffer cell to use.
|
|
989
|
+
:param placement: The coarse placement for this buffer (to be legalized.)
|
|
990
|
+
If unset, depending on whether the target is a driver or a sink:
|
|
991
|
+
|
|
992
|
+
- Driver: The placement will be the average of the driver and all sinks.
|
|
993
|
+
|
|
994
|
+
- Sink: The placement will be the average of the sink and all drivers.
|
|
995
|
+
"""
|
|
996
|
+
|
|
997
|
+
target: str
|
|
998
|
+
buffer: str
|
|
999
|
+
placement: Optional[Tuple[Decimal, Decimal]] = None
|
|
1000
|
+
|
|
1001
|
+
|
|
1002
|
+
@Step.factory.register()
|
|
1003
|
+
class InsertECOBuffers(OdbpyStep):
|
|
1004
|
+
"""
|
|
1005
|
+
Experimental step to insert ECO buffers on either drivers or sinks after
|
|
1006
|
+
global or detailed routing. The placement is legalized and global routing is
|
|
1007
|
+
incrementally re-run for affected nets. Useful for manually fixing some hold
|
|
1008
|
+
violations.
|
|
1009
|
+
|
|
1010
|
+
If run after detailed routing, detailed routing must be re-run as affected
|
|
1011
|
+
nets that are altered are removed and require re-routing.
|
|
1012
|
+
|
|
1013
|
+
INOUT and FEEDTHRU ports are not supported.
|
|
1014
|
+
"""
|
|
1015
|
+
|
|
1016
|
+
id = "Odb.InsertECOBuffers"
|
|
1017
|
+
name = "Insert ECO Buffers"
|
|
1018
|
+
|
|
1019
|
+
config_vars = (
|
|
1020
|
+
dpl_variables
|
|
1021
|
+
+ grt_variables
|
|
1022
|
+
+ [
|
|
1023
|
+
Variable(
|
|
1024
|
+
"INSERT_ECO_BUFFERS",
|
|
1025
|
+
Optional[List[ECOBuffer]],
|
|
1026
|
+
"List of buffers to insert",
|
|
1027
|
+
)
|
|
1028
|
+
]
|
|
1029
|
+
)
|
|
1030
|
+
|
|
1031
|
+
def get_script_path(self):
|
|
1032
|
+
return os.path.join(get_script_dir(), "odbpy", "eco_buffer.py")
|
|
1033
|
+
|
|
1034
|
+
def get_command(self) -> List[str]:
|
|
1035
|
+
assert self.config_path is not None, "get_command called before start()"
|
|
1036
|
+
return super().get_command() + ["--step-config", self.config_path]
|
|
1037
|
+
|
|
1038
|
+
|
|
1039
|
+
@dataclass
|
|
1040
|
+
class ECODiode:
|
|
1041
|
+
"""
|
|
1042
|
+
:param target: The sink whose net gets a diode connected, in the format
|
|
1043
|
+
instance_name/pin_name.
|
|
1044
|
+
:param placement: The coarse placement for this diode (to be legalized.)
|
|
1045
|
+
If unset, the diode is placed at the same location as the target
|
|
1046
|
+
instance, with legalization later moving it to a valid location.
|
|
1047
|
+
"""
|
|
1048
|
+
|
|
1049
|
+
target: str
|
|
1050
|
+
placement: Optional[Tuple[Decimal, Decimal]] = None
|
|
1051
|
+
|
|
1052
|
+
|
|
1053
|
+
@Step.factory.register()
|
|
1054
|
+
class InsertECODiodes(OdbpyStep):
|
|
1055
|
+
"""
|
|
1056
|
+
Experimental step to create and attach ECO diodes to the nets of sinks after
|
|
1057
|
+
global or detailed routing. The placement is legalized and global routing is
|
|
1058
|
+
incrementally re-run for affected nets. Useful for manually fixing some
|
|
1059
|
+
antenna violations.
|
|
1060
|
+
|
|
1061
|
+
If run after detailed routing, detailed routing must be re-run as affected
|
|
1062
|
+
nets that are altered are removed and require re-routing.
|
|
1063
|
+
"""
|
|
1064
|
+
|
|
1065
|
+
id = "Odb.InsertECODiodes"
|
|
1066
|
+
name = "Insert ECO Diodes"
|
|
1067
|
+
|
|
1068
|
+
config_vars = (
|
|
1069
|
+
grt_variables
|
|
1070
|
+
+ dpl_variables
|
|
1071
|
+
+ [
|
|
1072
|
+
Variable(
|
|
1073
|
+
"INSERT_ECO_DIODES",
|
|
1074
|
+
Optional[List[ECODiode]],
|
|
1075
|
+
"List of sinks to insert diodes for.",
|
|
1076
|
+
)
|
|
1077
|
+
]
|
|
1078
|
+
)
|
|
1079
|
+
|
|
1080
|
+
def get_script_path(self):
|
|
1081
|
+
return os.path.join(get_script_dir(), "odbpy", "eco_diode.py")
|
|
1082
|
+
|
|
1083
|
+
def get_command(self) -> List[str]:
|
|
1084
|
+
assert self.config_path is not None, "get_command called before start()"
|
|
1085
|
+
return super().get_command() + ["--step-config", self.config_path]
|
|
1086
|
+
|
|
1087
|
+
def run(self, state_in: State, **kwargs):
|
|
1088
|
+
if self.config["DIODE_CELL"] is None:
|
|
1089
|
+
info(f"'DIODE_CELL' not set. Skipping '{self.id}'…")
|
|
1090
|
+
return {}, {}
|
|
1091
|
+
return super().run(state_in, **kwargs)
|
librelane/steps/openroad.py
CHANGED
|
@@ -185,6 +185,8 @@ class OpenROADStep(TclStep):
|
|
|
185
185
|
|
|
186
186
|
output_processors = [OpenROADOutputProcessor, DefaultOutputProcessor]
|
|
187
187
|
|
|
188
|
+
alerts: Optional[List[OpenROADAlert]] = None
|
|
189
|
+
|
|
188
190
|
config_vars = [
|
|
189
191
|
Variable(
|
|
190
192
|
"PDN_CONNECT_MACROS_TO_GRID",
|
|
@@ -264,6 +266,7 @@ class OpenROADStep(TclStep):
|
|
|
264
266
|
2. After the `super()` call: Processes the `or_metrics_out.json` file and
|
|
265
267
|
updates the State's `metrics` property with any new metrics in that object.
|
|
266
268
|
"""
|
|
269
|
+
self.alerts = None
|
|
267
270
|
kwargs, env = self.extract_env(kwargs)
|
|
268
271
|
env = self.prepare_env(env, state_in)
|
|
269
272
|
|
|
@@ -293,9 +296,11 @@ class OpenROADStep(TclStep):
|
|
|
293
296
|
views_updates[output] = path
|
|
294
297
|
|
|
295
298
|
# 1. Parse warnings and errors
|
|
296
|
-
alerts = subprocess_result
|
|
299
|
+
self.alerts = subprocess_result.get("openroad_alerts") or []
|
|
297
300
|
if subprocess_result["returncode"] != 0:
|
|
298
|
-
error_strings = [
|
|
301
|
+
error_strings = [
|
|
302
|
+
str(alert) for alert in self.alerts if alert.cls == "error"
|
|
303
|
+
]
|
|
299
304
|
if len(error_strings):
|
|
300
305
|
error_string = "\n".join(error_strings)
|
|
301
306
|
raise StepError(
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: librelane
|
|
3
|
-
Version: 2.4.0.
|
|
3
|
+
Version: 2.4.0.dev3
|
|
4
4
|
Summary: An infrastructure for implementing chip design flows
|
|
5
|
+
Home-page: https://github.com/librelane/librelane
|
|
5
6
|
License: Apache-2.0
|
|
6
7
|
Author: Mohamed Gaber
|
|
7
8
|
Author-email: me@donn.website
|
|
@@ -19,8 +20,7 @@ Requires-Dist: click (>=8,<9)
|
|
|
19
20
|
Requires-Dist: cloup (>=3.0.5,<4)
|
|
20
21
|
Requires-Dist: deprecated (>=1.2.10,<2)
|
|
21
22
|
Requires-Dist: httpx (>=0.22.0,<0.29)
|
|
22
|
-
Requires-Dist:
|
|
23
|
-
Requires-Dist: klayout (>=0.29.0,<0.30.0)
|
|
23
|
+
Requires-Dist: klayout (>=0.29.0,<0.31.0)
|
|
24
24
|
Requires-Dist: libparse (>=0.3.1,<1)
|
|
25
25
|
Requires-Dist: lxml (>=4.9.0)
|
|
26
26
|
Requires-Dist: psutil (>=5.9.0)
|
|
@@ -49,8 +49,10 @@ Description-Content-Type: text/markdown
|
|
|
49
49
|
|
|
50
50
|
LibreLane is an ASIC infrastructure library based on several components including
|
|
51
51
|
OpenROAD, Yosys, Magic, Netgen, CVC, KLayout and a number of custom scripts for
|
|
52
|
-
design exploration and optimization, currently developed and maintained by
|
|
53
|
-
|
|
52
|
+
design exploration and optimization, currently developed and maintained by
|
|
53
|
+
members and affiliates of the
|
|
54
|
+
[American University in Cairo Open Hardware Lab](https://github.com/aucohl)
|
|
55
|
+
under the stewardship of the [FOSSi Foundation](https://fossi-foundation.org).
|
|
54
56
|
|
|
55
57
|
A reference flow, "Classic", performs all ASIC implementation steps from RTL all
|
|
56
58
|
the way down to GDSII.
|
|
@@ -142,10 +144,27 @@ If you use LibreLane in your research, please cite the following paper.
|
|
|
142
144
|
|
|
143
145
|
## License and Legal Info
|
|
144
146
|
|
|
145
|
-
LibreLane is a trademark of the FOSSi Foundation.
|
|
147
|
+
LibreLane is a trademark of the [FOSSi Foundation](https://fossi-foundation.org).
|
|
146
148
|
|
|
149
|
+
LibreLane code and binaries are available under
|
|
147
150
|
[The Apache License, version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt).
|
|
148
151
|
|
|
149
|
-
LibreLane is based on OpenLane 2.
|
|
150
|
-
|
|
152
|
+
LibreLane is based on [OpenLane 2](https://github.com/efabless/openlane2)
|
|
153
|
+
by Efabless Corporation:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
Copyright 2022-2025 Efabless Corporation
|
|
157
|
+
|
|
158
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
159
|
+
you may not use this file except in compliance with the License.
|
|
160
|
+
You may obtain a copy of the License at
|
|
161
|
+
|
|
162
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
163
|
+
|
|
164
|
+
Unless required by applicable law or agreed to in writing, software
|
|
165
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
166
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
167
|
+
See the License for the specific language governing permissions and
|
|
168
|
+
limitations under the License.
|
|
169
|
+
```
|
|
151
170
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
librelane/__init__.py,sha256=EMpoZrRmS_wsweKjhyAg52OXCK7HWQ8o8CVrYaX4ub0,1220
|
|
2
|
-
librelane/__main__.py,sha256=
|
|
2
|
+
librelane/__main__.py,sha256=48sW8mh1MNbG0Mf75f5LekXzerbaEASLInQSrvQqPw4,14802
|
|
3
3
|
librelane/__version__.py,sha256=dbE4stCACDmIoxgKksesAkTa-_hi5dW6nPLWw9Pfq3Q,1486
|
|
4
4
|
librelane/common/__init__.py,sha256=LrzxjZKJu3-i8oEYdXtV1IxkXicHtoSUNRcOGrGVGsw,1516
|
|
5
5
|
librelane/common/cli.py,sha256=Ob7mDR77qX1DZ7MXBQ4EPGS3dAO0o7b4njIloOxoB-Q,2351
|
|
@@ -18,14 +18,14 @@ librelane/common/tpe.py,sha256=Txj0fVscXSDJTYmEKZ2ESFHOeqrhHnaPPiwWBgyx4g8,1285
|
|
|
18
18
|
librelane/common/types.py,sha256=oclAQkeluz_iopI_28clHzxvac7gN5moT8Rzipy5mgM,3468
|
|
19
19
|
librelane/config/__init__.py,sha256=lbJmD5CbrrrnaNdIUWqFIK488ea0uyej3iExh-9mkgE,1107
|
|
20
20
|
librelane/config/__main__.py,sha256=6KSXxM4qNE2yJhizUsF1kdMsY1kY7hLHoPoz50POsS8,4532
|
|
21
|
-
librelane/config/config.py,sha256=
|
|
21
|
+
librelane/config/config.py,sha256=WUznKnVYLn7ZNbUL4YMkMX7akmyc2S26ksQSicKeN1c,34964
|
|
22
22
|
librelane/config/flow.py,sha256=qCGaUOj12j57gORzoE10m7_WG-n600llnFDMlZagUF4,16660
|
|
23
23
|
librelane/config/pdk_compat.py,sha256=rznq5xIny9M0PmddhPOGtCIrSdv98ysAoYgkpyM0gUA,8450
|
|
24
24
|
librelane/config/preprocessor.py,sha256=I239Y01dC2o5eb1UtcSbLdybVrZgqGyDr7ecT234I4Y,14913
|
|
25
25
|
librelane/config/removals.py,sha256=lJ0xpkCqnZAdA_ug4yq0NDjRBFuw4XsdORwymbEVGyQ,2907
|
|
26
|
-
librelane/config/variable.py,sha256=
|
|
27
|
-
librelane/container.py,sha256=
|
|
28
|
-
librelane/env_info.py,sha256=
|
|
26
|
+
librelane/config/variable.py,sha256=tqH7po203Q7usX9jdVoyng2fdKPzDO36UmbCPZcIcsA,26232
|
|
27
|
+
librelane/container.py,sha256=3KHxs3dUSVUZVYsS6fsA7dD3Q4QEQEzRxgXZZh9dzi0,7554
|
|
28
|
+
librelane/env_info.py,sha256=vAE9AZ_vDFLt7Srtg4ZywPzE6vgVhCrIvg8PP25-BJ8,10460
|
|
29
29
|
librelane/examples/spm/config.yaml,sha256=YKBm0lsY3AJZNcxAh1sQ1QMmJeVCpOpil6dw_RgQh4c,633
|
|
30
30
|
librelane/examples/spm/pin_order.cfg,sha256=-8mTGFKnES0vhQATfaE2TXN_mdCZ3SZIN90Src1l6fY,52
|
|
31
31
|
librelane/examples/spm/src/impl.sdc,sha256=wP18UoVlOJ9q4lmUoa3XpgcpPdyzEqHBNxCgOOU7QH0,2961
|
|
@@ -41,11 +41,11 @@ librelane/examples/spm-user_project_wrapper/template.def,sha256=7kl9l-oh4BDKuFHh
|
|
|
41
41
|
librelane/examples/spm-user_project_wrapper/user_project_wrapper.v,sha256=zc6GC583muuWtzw3p6v_B1k8j-Oo9WypuQf_8doA4uo,3364
|
|
42
42
|
librelane/flows/__init__.py,sha256=ghtmUG-taVpHJ3CKJRYZGn3dU0r93araT1EIGlBEsxg,896
|
|
43
43
|
librelane/flows/builtins.py,sha256=tR14Qc1ZUey2w-Ar4DWOvxuP7LGPtMecCJq8WgcYJpk,773
|
|
44
|
-
librelane/flows/classic.py,sha256=
|
|
44
|
+
librelane/flows/classic.py,sha256=fI-LNhrvi7lzfsHRyJv_yjgFbpbWBVxN-9QpsgDxpTQ,10940
|
|
45
45
|
librelane/flows/cli.py,sha256=P2LCFn5_RQ88yB0WuetpLAuWeKQXd-DrpCOMgnVh9Mg,16705
|
|
46
46
|
librelane/flows/flow.py,sha256=wyYw-w6NIbCCfyfgwiq3BpztLlvZkRFUUeePoI9DpaU,34167
|
|
47
47
|
librelane/flows/misc.py,sha256=32Om3isexesfKKiJZCajNmINc-xdv7eVx_tgoh9SR6U,2015
|
|
48
|
-
librelane/flows/optimizing.py,sha256=
|
|
48
|
+
librelane/flows/optimizing.py,sha256=OwZz6WGmXpliwO8vtmhjKHD-kzDyNv-zoCECZIigXsI,6076
|
|
49
49
|
librelane/flows/sequential.py,sha256=DLzgvHKq0cO-U-eLO98zIFRnhGLfRv80_ozSX973TlI,13350
|
|
50
50
|
librelane/flows/synth_explore.py,sha256=8mpeuG6oxeEXVQi4NwS4I415eCu7Ak6DN4oK30h1eCQ,7418
|
|
51
51
|
librelane/logging/__init__.py,sha256=mrTnzjpH6AOu2CiDZYfOMCVByAS2Xeg9HS4FJyXsJOE,1043
|
|
@@ -82,18 +82,21 @@ librelane/scripts/odbpy/apply_def_template.py,sha256=Tn6y65biu0bAQ6XilYxq5jn3a_K
|
|
|
82
82
|
librelane/scripts/odbpy/cell_frequency.py,sha256=NfGgM8wxIvjM1C_GHUghZPOh8gpdasLOWR4qBdHHLFE,3105
|
|
83
83
|
librelane/scripts/odbpy/check_antenna_properties.py,sha256=dMD-RcoA7plcAu9IIqa2e-8XCO0EMcKG-6P39D3Gpic,3942
|
|
84
84
|
librelane/scripts/odbpy/contextualize.py,sha256=G8EEgmK6ISikXD2-Pw-RTs1JxLWPnuppwL7oPfAsb84,4020
|
|
85
|
-
librelane/scripts/odbpy/defutil.py,sha256=
|
|
85
|
+
librelane/scripts/odbpy/defutil.py,sha256=g0UaAQRt8hXb9nfI6SbMp_Hx__0o1POw33_fS6xyibU,17849
|
|
86
86
|
librelane/scripts/odbpy/diodes.py,sha256=ZS_1niaTcwvaTTNjJcth4Qepcwxa6aV6E9WM_KiMtTI,11811
|
|
87
87
|
librelane/scripts/odbpy/disconnected_pins.py,sha256=hS_Iletg7N6K6yN_ccvWxZ3nWNZp4ecUJM-oY0kkEfA,11139
|
|
88
|
-
librelane/scripts/odbpy/
|
|
88
|
+
librelane/scripts/odbpy/eco_buffer.py,sha256=QOL2J0UJQiVvuGFbpdyAj-RRsPfEL-rT_qro3Rp0KeE,6256
|
|
89
|
+
librelane/scripts/odbpy/eco_diode.py,sha256=2LN7fHh9uO9JP3PYIxIwUiP1lyeqdTNF2ADTcY_Bu-g,4281
|
|
89
90
|
librelane/scripts/odbpy/filter_unannotated.py,sha256=Gvcaj_WNr6TPiHk-36nkMu4betNHZo1g2lD3UcA9hDQ,2950
|
|
90
91
|
librelane/scripts/odbpy/io_place.py,sha256=LSJIJQDLSOpENyQOg_kVTIbh1AbYLiHIXx0siduo-lg,15589
|
|
92
|
+
librelane/scripts/odbpy/ioplace_parser/__init__.py,sha256=TMKTIWwGJfdSr7dJcsoisUuKlbTKJdHV6-0kB77v2P8,887
|
|
93
|
+
librelane/scripts/odbpy/ioplace_parser/parse.py,sha256=LDncc8r1nDmcTCVtxqBu7xswiesVX6-snYiIKFB_kxs,5594
|
|
91
94
|
librelane/scripts/odbpy/label_macro_pins.py,sha256=n3o9-_g6HkVP8k49yNnCkQJms9f_ykCE0Rye7bVFtIk,8620
|
|
92
95
|
librelane/scripts/odbpy/lefutil.py,sha256=XhfWSGHdn96yZWYQAPisgJM0iuY3xw4SW7jmMTzbpZs,3064
|
|
93
96
|
librelane/scripts/odbpy/placers.py,sha256=mgy_-GYeLDPMG41YAopMTtJyCHP6ucJRk7cJzI9PLRQ,4572
|
|
94
97
|
librelane/scripts/odbpy/power_utils.py,sha256=al12uMiv8G0yQZOPKXNHYQ1dm2KGlu9xigSuYLEAo_A,14627
|
|
95
98
|
librelane/scripts/odbpy/random_place.py,sha256=TEsV4LtXQTP8OJvnBh09Siu9fKkwG9UpIkCkQpdXAgU,1649
|
|
96
|
-
librelane/scripts/odbpy/reader.py,sha256=
|
|
99
|
+
librelane/scripts/odbpy/reader.py,sha256=5-hpG3TgmKwJtnbEDQoAPejeDx1QyRBRbIMULMkBesM,8539
|
|
97
100
|
librelane/scripts/odbpy/remove_buffers.py,sha256=f-kGZIPnMtu4gnl2r2CDkng8U8vUMJKJWNV_akOpc38,5460
|
|
98
101
|
librelane/scripts/odbpy/snap_to_grid.py,sha256=lULRWlcYXvrTBUpemUPlpO2dBnbFeriuG-DlI4KnViE,1743
|
|
99
102
|
librelane/scripts/odbpy/wire_lengths.py,sha256=pSPhVnLlvcvmgEh89G8nu8DRaZVP66r-4ieVoV3zrm4,2737
|
|
@@ -104,7 +107,7 @@ librelane/scripts/openroad/buffer_list.tcl,sha256=sXygy1KRSUS4dZi1UOpBkGGOuXRVLM
|
|
|
104
107
|
librelane/scripts/openroad/common/dpl.tcl,sha256=Nqq5e5OwRoyk8mHfVa5uw3DGKDGMEFrx6odpxanc_Ns,912
|
|
105
108
|
librelane/scripts/openroad/common/dpl_cell_pad.tcl,sha256=KWVuj8u1-y3ZUiQr48TAsFv1GSzOCVnAjdqfBjtoQxQ,1066
|
|
106
109
|
librelane/scripts/openroad/common/grt.tcl,sha256=DCKe5D7INCBctitbRdgZNRsBrI9Qo5v7Ag7DF45W4_U,1137
|
|
107
|
-
librelane/scripts/openroad/common/io.tcl,sha256=
|
|
110
|
+
librelane/scripts/openroad/common/io.tcl,sha256=cPJ4O2nBKrGKON7lO7ZX1j_TpcxQFCw3gH858yTWg8Q,18289
|
|
108
111
|
librelane/scripts/openroad/common/pdn_cfg.tcl,sha256=KnQAxzlL_261Kp4M02cQ6usZHIRNBj56SAZNn1CqrZc,4552
|
|
109
112
|
librelane/scripts/openroad/common/resizer.tcl,sha256=3p59NDcXgEkUzg_43dcUYr2KkD__fkADdqCZ3uWsUU4,3478
|
|
110
113
|
librelane/scripts/openroad/common/set_global_connections.tcl,sha256=zGMz0Hu57ZVdLhz4djJASyre0qOi-dszylo6HD8ClUM,2899
|
|
@@ -143,8 +146,8 @@ librelane/state/__init__.py,sha256=rLUdAkeB278r8pB2Jpv-ccmmmP32FR90wANIFHXdA0w,9
|
|
|
143
146
|
librelane/state/__main__.py,sha256=Ici4Ejg1ICUZNSYZRguC3BfEk_wFxsmE0ag0Vv8iY1I,1679
|
|
144
147
|
librelane/state/design_format.py,sha256=9mWBbHrhzludtv3dKB6P4UQncBVTMMtoj69kNgRvh5o,5306
|
|
145
148
|
librelane/state/state.py,sha256=J05gAeSVDiF76ITuw4WJZ7WkMyG4oTjt_7kpsI3E3PE,11957
|
|
146
|
-
librelane/steps/__init__.py,sha256=
|
|
147
|
-
librelane/steps/__main__.py,sha256=
|
|
149
|
+
librelane/steps/__init__.py,sha256=j3JYrdnWM74dYuEvE931oSrQI7FUz-hKWr8Mts8C0wg,1668
|
|
150
|
+
librelane/steps/__main__.py,sha256=GviXtDLISKJCufKxK3oFPOSMF1GyShZbG5RXpVCYFkk,13376
|
|
148
151
|
librelane/steps/checker.py,sha256=vul1D0cT03144qKK5QAKswClKKICK7kNB4PB6xXykvc,21353
|
|
149
152
|
librelane/steps/common_variables.py,sha256=qT0VeIstFsDbe8VGAyqXrXxQw69OZ08haM6u1IbdFiE,10429
|
|
150
153
|
librelane/steps/cvc_rv.py,sha256=32vxFIbzSbrDtE0fXvdoQ-v3LVMrfi3r88f8Y-TKPKg,5531
|
|
@@ -152,15 +155,15 @@ librelane/steps/klayout.py,sha256=g7jYz-1cLwgfPTiMJIdAQ9zmkrwNtJLPoRg6PqOUv6Y,16
|
|
|
152
155
|
librelane/steps/magic.py,sha256=4o_WarBAQdTTuekP72uovjvqW5wsaDCpMB3LtAhC_IY,20051
|
|
153
156
|
librelane/steps/misc.py,sha256=Xk_a6JJPljkk8pemu-NtlzDRs-8S7vuRKZKj4pnCRlE,5690
|
|
154
157
|
librelane/steps/netgen.py,sha256=R9sDWv-9wKMdi2rkuLQdOc4uLlbYhXcKKd6WsZsnLt0,8953
|
|
155
|
-
librelane/steps/odb.py,sha256=
|
|
156
|
-
librelane/steps/openroad.py,sha256=
|
|
158
|
+
librelane/steps/odb.py,sha256=_WhAFEVbFioSGsVrGbXQVqcXYAnE22gLA4eF1v028EQ,38030
|
|
159
|
+
librelane/steps/openroad.py,sha256=0uHbFYO3wT9rFkovxMgr4rMzaMpA9DvG8sWLM4L3jn8,85836
|
|
157
160
|
librelane/steps/openroad_alerts.py,sha256=IJyB4piBDCKXhkJswHGMYCRDwbdQsR0GZlrGGDhmW6Q,3364
|
|
158
161
|
librelane/steps/pyosys.py,sha256=bBKCIiKxbAQXrEgR1gdg2el-IpuZoUzLOwR62-ilwQg,22326
|
|
159
162
|
librelane/steps/step.py,sha256=YkUWbxx2dOz0QR90jM5NC7neFkWO1RW5zJc0I_XxRyc,54975
|
|
160
163
|
librelane/steps/tclstep.py,sha256=0PMWJ6C3dKnlQf9mA9rZntgxUBCiByE9csHcEcM1iq0,10027
|
|
161
164
|
librelane/steps/verilator.py,sha256=MWx2TpLqYyea9_jSeLG9c2S5ujvYERQZRFNaMhfHxZE,7916
|
|
162
165
|
librelane/steps/yosys.py,sha256=GX6rTiQG-ZhDxfB9SxrPQ9Sab3WC84p0OUtqiL1Nubk,12533
|
|
163
|
-
librelane-2.4.0.
|
|
164
|
-
librelane-2.4.0.
|
|
165
|
-
librelane-2.4.0.
|
|
166
|
-
librelane-2.4.0.
|
|
166
|
+
librelane-2.4.0.dev3.dist-info/METADATA,sha256=ycb_5xJHsFD3ETcy_FObKWoWc64Ck0BpJ4cGB2Ts3DE,6600
|
|
167
|
+
librelane-2.4.0.dev3.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
168
|
+
librelane-2.4.0.dev3.dist-info/entry_points.txt,sha256=GTBvXykNMMFsNKiJFgtEw7P1wb_VZIqVM35EFSpyZQE,263
|
|
169
|
+
librelane-2.4.0.dev3.dist-info/RECORD,,
|
|
File without changes
|