hwcomponents-adc 0.1__tar.gz

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.
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: hwcomponents-adc
3
+ Version: 0.1
4
+ Summary: A package for estimating the energy and area of Analog-Digital Converters
5
+ Author-email: Tanner Andrulis <Andrulis@mit.edu>
6
+ License: MIT
7
+ Keywords: accelerator,hardware,energy,estimation,analog,adc
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
12
+ Requires-Python: >=3.12
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: PyYAML
15
+ Requires-Dist: numpy
16
+ Requires-Dist: pandas
17
+
18
+ # HWComponents-ADC
19
+ HWComponents-ADC models the area and energy of Analog-Digital Converters (ADCs) for use
20
+ in analog & mixed-signal accelerator designs.
21
+
22
+ Models are based on statistical analysis of published ADC performance data in Boris
23
+ Murmann's ADC Performance Survey [1]. The energy model is based on the observation that
24
+ the maximum efficiency of an ADC is bounded by the sampling rate and the resolution [1],
25
+ and the area model is based on regression analysis. Estimations are optimistic; they
26
+ answer the question "what is the best possible ADC design for the given parameters?".
27
+
28
+ ## Installation
29
+ Clone the repository and install with pip:
30
+
31
+ ```bash
32
+ git clone https://github.com/Accelergy-Project/hwcomponents-adc.git
33
+ cd hwcomponents-adc
34
+ pip install .
35
+
36
+ # Check that the installation is successful
37
+ hwc --list | grep ADC
38
+ ```
39
+
40
+ ## Usage
41
+ ### Inerface
42
+
43
+ This model introduces the ADC model. ADC models can be instantiated with the
44
+ following parameters:
45
+ - `n_bits`: the resolution of the ADC
46
+ - `tech_node`: the technology node in meters
47
+ - `n_adcs`: the number of ADCs working together, in the case of alternating
48
+ ADCs
49
+ - `throughput`: the aggregate throughput of the ADCs, in samples per second
50
+
51
+ ADCs support the following actions:
52
+ - `read` or `convert`: Convert a single value from analog to digital. Note: if
53
+ there are multiple ADCs, this is a single conversion from a single ADC.
54
+
55
+ ### Exploring Tradeoffs
56
+ There are several tradeoffs available around ADC design:
57
+ - Lower-resolution ADCs are smaller and more energy-efficient.
58
+ - Using more ADCs in parallel allows for a lower frequency, but increases the
59
+ area.
60
+ - Using fewer ADCs in parallel allows for a higher frequency. Up to a point,
61
+ this will not increase the area or energy/area of the ADCs. However, at some
62
+ this will result in an exponential increase in energy/area.
63
+ - Lower-resolution ADCs can run at higher frequencies before the exponential
64
+ increase in energy/area occurs.
65
+
66
+ When the HWComponents-ADC runs, it will output a list of alternative design options.
67
+ Each will report a number of ADCs and frequency needed to achieve the desired
68
+ throughput, as well as the area and energy of the ADCs. You can then use this
69
+ information to make tradeoffs between ADC resolution, frequency, and number of
70
+ ADCs.
71
+
72
+ HWComponents-ADC is the work of Tanner Andrulis & Ruicong Chen.
73
+
74
+ ## Updating the ADC Model
75
+ The generated ADC model is based on the data in Boris Murmann's survey [1],
76
+ included in the submodule. This survey is updated periodically. The model can
77
+ be update to reflect the most recent data by running the following:
78
+
79
+ ```bash
80
+ pip3 install scikit-learn
81
+ pip3 install pandas
82
+ pip3 install numpy
83
+ git submodule update --init --recursive --remote
84
+ python3 update_model.py
85
+ ```
86
+
87
+ This is only necessary if more recent data is published. If the data here is
88
+ out of date, please open an issue or pull request.
89
+
90
+ ## References
91
+ [1] B. Murmann, "ADC Performance Survey 1997-2023," [Online]. Available:
92
+ https://github.com/bmurmann/ADC-survey
93
+
94
+ ## License
95
+ This work is licensed under the MIT license. See license.txt for details.
96
+
97
+ ## Citing HWComponents-ADC
98
+ If you use this model in your work, please cite the following:
99
+
100
+ ```bibtex
101
+ @misc{andrulis2024modelinganalogdigitalconverterenergyarea,
102
+ title={Modeling Analog-Digital-Converter Energy and Area for Compute-In-Memory Accelerator Design},
103
+ author={Tanner Andrulis and Ruicong Chen and Hae-Seung Lee and Joel S. Emer and Vivienne Sze},
104
+ year={2024},
105
+ eprint={2404.06553},
106
+ archivePrefix={arXiv},
107
+ primaryClass={cs.AR},
108
+ url={https://arxiv.org/abs/2404.06553},
109
+ }
110
+ ```
@@ -0,0 +1,93 @@
1
+ # HWComponents-ADC
2
+ HWComponents-ADC models the area and energy of Analog-Digital Converters (ADCs) for use
3
+ in analog & mixed-signal accelerator designs.
4
+
5
+ Models are based on statistical analysis of published ADC performance data in Boris
6
+ Murmann's ADC Performance Survey [1]. The energy model is based on the observation that
7
+ the maximum efficiency of an ADC is bounded by the sampling rate and the resolution [1],
8
+ and the area model is based on regression analysis. Estimations are optimistic; they
9
+ answer the question "what is the best possible ADC design for the given parameters?".
10
+
11
+ ## Installation
12
+ Clone the repository and install with pip:
13
+
14
+ ```bash
15
+ git clone https://github.com/Accelergy-Project/hwcomponents-adc.git
16
+ cd hwcomponents-adc
17
+ pip install .
18
+
19
+ # Check that the installation is successful
20
+ hwc --list | grep ADC
21
+ ```
22
+
23
+ ## Usage
24
+ ### Inerface
25
+
26
+ This model introduces the ADC model. ADC models can be instantiated with the
27
+ following parameters:
28
+ - `n_bits`: the resolution of the ADC
29
+ - `tech_node`: the technology node in meters
30
+ - `n_adcs`: the number of ADCs working together, in the case of alternating
31
+ ADCs
32
+ - `throughput`: the aggregate throughput of the ADCs, in samples per second
33
+
34
+ ADCs support the following actions:
35
+ - `read` or `convert`: Convert a single value from analog to digital. Note: if
36
+ there are multiple ADCs, this is a single conversion from a single ADC.
37
+
38
+ ### Exploring Tradeoffs
39
+ There are several tradeoffs available around ADC design:
40
+ - Lower-resolution ADCs are smaller and more energy-efficient.
41
+ - Using more ADCs in parallel allows for a lower frequency, but increases the
42
+ area.
43
+ - Using fewer ADCs in parallel allows for a higher frequency. Up to a point,
44
+ this will not increase the area or energy/area of the ADCs. However, at some
45
+ this will result in an exponential increase in energy/area.
46
+ - Lower-resolution ADCs can run at higher frequencies before the exponential
47
+ increase in energy/area occurs.
48
+
49
+ When the HWComponents-ADC runs, it will output a list of alternative design options.
50
+ Each will report a number of ADCs and frequency needed to achieve the desired
51
+ throughput, as well as the area and energy of the ADCs. You can then use this
52
+ information to make tradeoffs between ADC resolution, frequency, and number of
53
+ ADCs.
54
+
55
+ HWComponents-ADC is the work of Tanner Andrulis & Ruicong Chen.
56
+
57
+ ## Updating the ADC Model
58
+ The generated ADC model is based on the data in Boris Murmann's survey [1],
59
+ included in the submodule. This survey is updated periodically. The model can
60
+ be update to reflect the most recent data by running the following:
61
+
62
+ ```bash
63
+ pip3 install scikit-learn
64
+ pip3 install pandas
65
+ pip3 install numpy
66
+ git submodule update --init --recursive --remote
67
+ python3 update_model.py
68
+ ```
69
+
70
+ This is only necessary if more recent data is published. If the data here is
71
+ out of date, please open an issue or pull request.
72
+
73
+ ## References
74
+ [1] B. Murmann, "ADC Performance Survey 1997-2023," [Online]. Available:
75
+ https://github.com/bmurmann/ADC-survey
76
+
77
+ ## License
78
+ This work is licensed under the MIT license. See license.txt for details.
79
+
80
+ ## Citing HWComponents-ADC
81
+ If you use this model in your work, please cite the following:
82
+
83
+ ```bibtex
84
+ @misc{andrulis2024modelinganalogdigitalconverterenergyarea,
85
+ title={Modeling Analog-Digital-Converter Energy and Area for Compute-In-Memory Accelerator Design},
86
+ author={Tanner Andrulis and Ruicong Chen and Hae-Seung Lee and Joel S. Emer and Vivienne Sze},
87
+ year={2024},
88
+ eprint={2404.06553},
89
+ archivePrefix={arXiv},
90
+ primaryClass={cs.AR},
91
+ url={https://arxiv.org/abs/2404.06553},
92
+ }
93
+ ```
@@ -0,0 +1 @@
1
+ from .main import ADC as ADC
@@ -0,0 +1,40 @@
1
+ frequency (Hz):
2
+ max value: 23.025850929940457
3
+ FOMS_hf [dB]:
4
+ frequency (Hz): -2.8719030399529957
5
+ max value: 164.7143258245858
6
+ intercept: 213.7806687348118
7
+ max_by_enob:
8
+ - 128.3068287398817
9
+ - 131.52734025085925
10
+ - 134.7478517618368
11
+ - 137.96836327281437
12
+ - 141.18887478379193
13
+ - 144.4093862947695
14
+ - 147.62989780574708
15
+ - 150.85040931672464
16
+ - 154.0709208277022
17
+ - 157.29143233867975
18
+ - 160.5119438496573
19
+ - 163.73245536063487
20
+ - 166.95296687161243
21
+ - 170.17347838259
22
+ - 173.39398989356755
23
+ - 176.61450140454514
24
+ - 179.83501291552267
25
+ - 183.05552442650026
26
+ - 186.27603593747781
27
+ - 189.49654744845537
28
+ - 192.71705895943293
29
+ tech intercept: 26.0496215686518
30
+ tech slope: 1.8800369206815004
31
+ enob slope: -2.8291256050594527
32
+ energy (pJ/op) res: -1.4702022133784405
33
+ comments: Tech node, area, and frequency are log-base-e-scaled. max_by_enob was also
34
+ considered for limiting the frequency of ADCs, but max ADC frequency was plenty
35
+ for realistic PIM settings at reasonable ADC resolutions.
36
+ area (um^2):
37
+ tech node (nm): 0.9932794744745022
38
+ frequency (Hz): 0.18071538424508238
39
+ energy (pJ/op): 0.29912426192653485
40
+ intercept: 1.3461916439910742
@@ -0,0 +1,68 @@
1
+ """
2
+ This file includes constants and headers that are used across the different
3
+ scripts in this plugin.
4
+ """
5
+
6
+ import math
7
+ import os
8
+
9
+ # ==============================================================================
10
+ # Design parameters
11
+ # ==============================================================================
12
+ FREQ = "frequency (Hz)" # Hz
13
+ TECH = "tech node (nm)" # nm
14
+ ENOB = "number of bits" # bits
15
+ DESIGN_PARAMS = [FREQ, TECH, ENOB]
16
+ AREA = "area (um^2)" # ln(um^2)
17
+ ENRG = "energy (pJ/op)" # pJ / op
18
+ FOMS = "FOMS_hf [dB]"
19
+ SNDR = "SNDR_plot [dB]"
20
+ ALL_PARAMS = DESIGN_PARAMS + [AREA, ENRG, FOMS]
21
+ AREA_CALCULATE_PARAMS = [TECH, FREQ, ENRG]
22
+ LOGSCALE_PARAMS = [FREQ, TECH, AREA, ENRG]
23
+
24
+ # For fitted energy / area
25
+ ENRG_RESIDUAL = f"{ENRG} res"
26
+ INTERCEPT = "intercept" # Constant factor for fitting
27
+ TECH_INTERCEPT = "tech intercept"
28
+ TECH_SLOPE = "tech slope"
29
+ ENOB_SLOPE = "enob slope"
30
+
31
+ # ==============================================================================
32
+ # Model file
33
+ # ==============================================================================
34
+ MIN = "min value"
35
+ MAX = "max value"
36
+ MAX_BY_ENOB = "max_by_enob"
37
+ AREA_ENRG_TRADEOFF = "area/energy tradeoff"
38
+ AREA_ENRG_MODEL = "model"
39
+ AREA_COEFF = "area coeff"
40
+ CONSTRAINTS = "constraints"
41
+ DESIGN_PARAM_MODEL = "design param model"
42
+ COMMENTS = "comments"
43
+ AREA_QUANTILE = 0.1 # Use bottom 10% of area
44
+
45
+ # ==============================================================================
46
+ # Paths
47
+ # ==============================================================================
48
+ CURRENT_DIR = os.path.dirname(__file__)
49
+ ADC_LIST_DEFAULT = os.path.join(CURRENT_DIR, "adc_data/adc_list.csv")
50
+ MODEL_DEFAULT = os.path.join(CURRENT_DIR, "adc_data/model.yaml")
51
+
52
+
53
+ # ==============================================================================
54
+ # Helper functions
55
+ # ==============================================================================
56
+ def dict_key_true(dict_to_check: dict, key: str) -> bool:
57
+ """Returns true if key in is dict and is not none"""
58
+ return dict_to_check and key in dict_to_check and dict_to_check[key]
59
+
60
+
61
+ def bits2sndr(bits: int) -> float:
62
+ """Calculates the SNDR of an ADC from its resolution"""
63
+ return bits * 20 * math.log(2, 10) + 10 * math.log(1.5, 10)
64
+
65
+
66
+ def sndr2bits(sndr: float) -> float:
67
+ """Calculates the resolution of an ADC from its sndr"""
68
+ return (sndr - 10 * math.log(1.5, 10)) / (20 * math.log(2, 10))
@@ -0,0 +1,222 @@
1
+ import logging
2
+ import sys
3
+ import os
4
+ import re
5
+ from typing import Dict, List
6
+ import yaml
7
+ from hwcomponents_adc.headers import *
8
+ from .optimizer import ADCRequest
9
+ from hwcomponents import EnergyAreaModel, actionDynamicEnergy
10
+
11
+ SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
12
+
13
+ MODEL_FILE = os.path.join(SCRIPT_DIR, "adc_data/model.yaml")
14
+
15
+ CLASS_NAMES = [
16
+ "adc",
17
+ "pim_adc",
18
+ "sar_adc",
19
+ "array_adc",
20
+ "pim_array_adc",
21
+ "cim_array_adc",
22
+ "cim_adc",
23
+ ]
24
+ ACTION_NAMES = ["convert", "drive", "read", "sample", "leak", "activate"]
25
+
26
+ # ==============================================================================
27
+ # Input Parsing
28
+ # ==============================================================================
29
+
30
+
31
+ def adc_attr_to_request(attributes: Dict, logger: logging.Logger) -> ADCRequest:
32
+ """Creates an ADC Request from a list of attributes"""
33
+
34
+ def checkerr(attr, numeric):
35
+ assert attr in attributes, f"No attribute found: {attr}"
36
+ if numeric and isinstance(attributes[attr], str):
37
+ v = re.findall(r"(\d*\.?\d+|\d+\.?\d*)", attributes[attr])
38
+ assert v, f"No numeric found for attribute: {attr}"
39
+ return float(v[0])
40
+ return attributes[attr]
41
+
42
+ try:
43
+ n_adcs = int(checkerr("n_adcs", numeric=True))
44
+ except AssertionError:
45
+ n_adcs = 1
46
+
47
+ def try_check(keys, numeric):
48
+ for k in keys[:-1]:
49
+ try:
50
+ return checkerr(k, numeric)
51
+ except AssertionError:
52
+ pass
53
+ return checkerr(keys[-1], numeric)
54
+
55
+ resolution_names = []
56
+ for x0 in ["adc", ""]:
57
+ for x1 in ["resolution", "bits", "n_bits"]:
58
+ for x2 in ["adc", ""]:
59
+ x = "_".join([x for x in [x0, x1, x2] if x != ""])
60
+ resolution_names.append(x)
61
+ resolution_names.append("resolution")
62
+
63
+ r = ADCRequest(
64
+ bits=try_check(resolution_names, numeric=True),
65
+ tech=float(checkerr("tech_node", numeric=True)),
66
+ throughput=float(checkerr("throughput", numeric=True)),
67
+ n_adcs=n_adcs,
68
+ logger=logger,
69
+ )
70
+ return r
71
+
72
+
73
+ def dict_to_str(attributes: Dict) -> str:
74
+ """Converts a dictionary into a multi-line string representation"""
75
+ s = "\n"
76
+ for k, v in attributes.items():
77
+ s += f"\t{k}: {v}\n"
78
+ return s
79
+
80
+
81
+ class ADC(EnergyAreaModel):
82
+ """
83
+ Analog digital converter (ADC) model based on https://arxiv.org/abs/2404.06553.
84
+
85
+ Args:
86
+ n_bits: The number of bits of the ADC. For those who are not familiar with ADCs,
87
+ ignore the following: this is assumed to be the effective number of bits
88
+ (ENOB), not the bit precision of the output.
89
+ tech_node: The technology node of the ADC in meters.
90
+ throughput: The throughput of the ADC in samples per second.
91
+ n_adcs: The number of ADCs. If there is >1 ADC, then throughput is the total
92
+ throughput of all ADCs.
93
+
94
+ Attributes:
95
+ n_bits: The number of bits of the ADC.
96
+ tech_node: The technology node of the ADC in meters.
97
+ throughput: The throughput of the ADC in samples per second.
98
+ n_adcs: The number of ADCs.
99
+ """
100
+ component_name = [
101
+ "adc",
102
+ "pim_adc",
103
+ "sar_adc",
104
+ "array_adc",
105
+ "pim_array_adc",
106
+ "cim_array_adc",
107
+ "cim_adc",
108
+ ]
109
+ priority = 0.75
110
+
111
+ def __init__(self, n_bits: int, tech_node: float, throughput: float, n_adcs=1):
112
+ self.n_bits = n_bits
113
+ self.tech_node = tech_node
114
+ self.throughput = throughput
115
+ self.n_adcs = n_adcs
116
+
117
+ self._model = self.make_model()
118
+
119
+ assert self.n_bits >= 4, f"Bits must be >= 4"
120
+
121
+ area = self._get_area()
122
+ # Assume leakage is 20% of the total energy
123
+ leak_power = self.get_energy() * self.throughput * 0.2
124
+ super().__init__(leak_power=leak_power, area=area)
125
+
126
+ def make_model(self):
127
+ if not os.path.exists(MODEL_FILE):
128
+ self.logger.info(f'python3 {os.path.join(SCRIPT_DIR, "run.py")} -g')
129
+ os.system(f'python3 {os.path.join(SCRIPT_DIR, "run.py")} -g')
130
+ if not os.path.exists(MODEL_FILE):
131
+ self.logger.error(f"ERROR: Could not find model file: {MODEL_FILE}")
132
+ self.logger.error(
133
+ f'Try running: "python3 {os.path.join(SCRIPT_DIR, "run.py")} '
134
+ f'-g" to generate a model.'
135
+ )
136
+ with open(MODEL_FILE, "r") as f:
137
+ self._model = yaml.safe_load(f)
138
+ return self._model
139
+
140
+ def _get_area(self) -> float:
141
+ """
142
+ Returns the area of the ADC in um^2
143
+ """
144
+ request = adc_attr_to_request(
145
+ {
146
+ "n_bits": self.n_bits,
147
+ "tech_node": self.tech_node,
148
+ "throughput": self.throughput,
149
+ "n_adcs": self.n_adcs,
150
+ },
151
+ self.logger,
152
+ )
153
+ return request.area(self._model) * 1e-12 # um^2 -> m^2
154
+
155
+ def get_energy(self):
156
+ """
157
+ Returns the energy for one ADC conversion in Joules.
158
+
159
+ Returns:
160
+ The energy for one ADC conversion in Joules.
161
+ """
162
+ request = adc_attr_to_request(
163
+ {
164
+ "n_bits": self.n_bits,
165
+ "tech_node": self.tech_node,
166
+ "throughput": self.throughput,
167
+ "n_adcs": self.n_adcs,
168
+ },
169
+ self.logger,
170
+ )
171
+ return request.energy_per_op(self._model) * 1e-12 # pJ -> J
172
+
173
+ @actionDynamicEnergy
174
+ def convert(self):
175
+ """
176
+ Returns the energy for one ADC conversion in Joules.
177
+
178
+ Returns:
179
+ The energy for one ADC conversion in Joules.
180
+ """
181
+ # Assume leakage is 20% of the total energy
182
+ return self.get_energy() * 0.8
183
+
184
+ @actionDynamicEnergy
185
+ def drive(self):
186
+ """
187
+ Returns the energy for one ADC conversion in Joules.
188
+
189
+ Returns:
190
+ The energy for one ADC conversion in Joules.
191
+ """
192
+ return self.convert()
193
+
194
+ @actionDynamicEnergy
195
+ def read(self):
196
+ """
197
+ Returns the energy for one ADC conversion in Joules.
198
+
199
+ Returns:
200
+ The energy for one ADC conversion in Joules.
201
+ """
202
+ return self.convert()
203
+
204
+ @actionDynamicEnergy
205
+ def sample(self):
206
+ """
207
+ Returns the energy for one ADC conversion in Joules.
208
+
209
+ Returns:
210
+ The energy for one ADC conversion in Joules.
211
+ """
212
+ return self.convert()
213
+
214
+ @actionDynamicEnergy
215
+ def activate(self):
216
+ """
217
+ Returns the energy for one ADC conversion in Joules.
218
+
219
+ Returns:
220
+ The energy for one ADC conversion in Joules.
221
+ """
222
+ return self.convert()
@@ -0,0 +1,289 @@
1
+ from typing import Dict, Tuple
2
+ import yaml
3
+
4
+ from hwcomponents_adc.headers import *
5
+ import logging
6
+ import math
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ def foms_sndr2energy(foms: float, sndr: float) -> float:
12
+ """Calculates the energy of an ADC from its Schreier FOM and SNDR"""
13
+ return 1 / ((10 ** ((foms - sndr) / 10)) * 2)
14
+
15
+
16
+ def get_area(params: Dict, model: Dict) -> float:
17
+ """
18
+ Returns the energy/convert of an ADC given its design parameters.
19
+ """
20
+ params = params.copy()
21
+ params[INTERCEPT] = 1 # Multiply model intercept by 1
22
+ assert all(k in params for k in model[AREA].keys()), (
23
+ f"Design parameters and model parameters do not match. Please "
24
+ f"regenergate ADC model."
25
+ )
26
+ return math.exp(sum(params[k] * model[AREA][k] for k in model[AREA].keys()))
27
+
28
+
29
+ def get_energy(params: Dict, model: Dict, allow_extrapolation: bool) -> float:
30
+ """
31
+ Returns the energy/convert of an ADC given its design parameters.
32
+ """
33
+ params = params.copy()
34
+ params[INTERCEPT] = 1 # Multiply model intercept by 1
35
+ warning_txt = (
36
+ f"Frequency {math.exp(params[FREQ]):2E} is greater than the maximum "
37
+ f"frequency {math.exp(model[FREQ][MAX]):2E} in the model."
38
+ )
39
+ err_txt = warning_txt + " Please use a lower frequency ADC or enable extrapolation."
40
+ if not allow_extrapolation:
41
+ assert params[FREQ] <= model[FREQ][MAX], err_txt
42
+ elif params[FREQ] > model[FREQ][MAX]:
43
+ logger.warning(warning_txt)
44
+
45
+ foms = sum(params[k] * model[FOMS][k] for k in [INTERCEPT, FREQ])
46
+ foms_max_by_enob = model[FOMS][MAX_BY_ENOB]
47
+ foms_max = foms_max_by_enob[
48
+ max(min(math.ceil(params[ENOB]), len(foms_max_by_enob) - 1), 0)
49
+ ]
50
+ foms = min(foms, foms_max)
51
+ energy = foms_sndr2energy(foms, bits2sndr(params[ENOB]))
52
+
53
+ return energy * math.exp(
54
+ model[FOMS][TECH_INTERCEPT]
55
+ + model[FOMS][TECH_SLOPE] * (params[TECH])
56
+ + model[FOMS][ENOB_SLOPE] * math.log(params[ENOB])
57
+ + model[FOMS][ENRG_RESIDUAL]
58
+ )
59
+
60
+
61
+ def mvgress(
62
+ x: "pd.DataFrame", y: "pd.Series"
63
+ ) -> Tuple["pd.Series", "np.ndarray", float]:
64
+ """
65
+ Returns the residuals, coeffs, and intercept of a multivariate linear
66
+ regression.
67
+ """
68
+ from sklearn import linear_model
69
+ import pandas as pd
70
+
71
+ if isinstance(x, pd.Series):
72
+ x = pd.DataFrame(x)
73
+ lm = linear_model.LinearRegression()
74
+ x_ = x
75
+ lm.fit(x_, y)
76
+ logger.info(f"Correlation: {lm.score(x_, y)}")
77
+ return y - lm.predict(x_), lm.coef_, lm.intercept_
78
+
79
+
80
+ def get_pareto(
81
+ x: "pd.Series",
82
+ y: "pd.Series",
83
+ x_positive=True,
84
+ y_positive=True,
85
+ allow_interior_points: int = 1,
86
+ ) -> "pd.DataFrame":
87
+ """
88
+ Returns the pareto set of x, y.
89
+ """
90
+ assert len(x) == len(y), "x and y must be the same length"
91
+
92
+ def more_value(a, b, pos):
93
+ return a >= b if pos else a <= b
94
+
95
+ chosen = []
96
+ for i in range(len(x)):
97
+ if (
98
+ len(x)
99
+ - sum(
100
+ more_value(x[i], x[j], x_positive) or more_value(y[i], y[j], y_positive)
101
+ for j in range(len(x))
102
+ )
103
+ < allow_interior_points
104
+ ):
105
+ chosen.append(i)
106
+
107
+ return x[chosen], y[chosen]
108
+
109
+
110
+ def build_model(
111
+ source_data: "pd.DataFrame", output_file: str, show_pretty_plot=False
112
+ ) -> Dict:
113
+ import numpy as np
114
+ import pandas as pd
115
+
116
+ # Ensure adc_data is valid
117
+ logger.info("Building model. Source adc_data:")
118
+ logger.info(source_data.head(5))
119
+ for c in ALL_PARAMS:
120
+ assert c in source_data, f"Missing adc_data column {c}!"
121
+ for index, row in source_data.iterrows():
122
+ for c in ALL_PARAMS:
123
+ assert isinstance(
124
+ row[c], float
125
+ ), f"Invalid value in row {index} column {c}: {row[c]}"
126
+
127
+ # Log columns
128
+ logscale_data = source_data.copy()
129
+ for c in LOGSCALE_PARAMS:
130
+ logscale_data[c] = np.log(source_data[c])
131
+
132
+ # Remove outliers
133
+ OUTLIER_THRESHOLD = 0.8
134
+
135
+ # Adjust the FOMS by tech node
136
+ residual, coeffs, intercept = mvgress(logscale_data[ENOB], logscale_data[TECH])
137
+ logscale_data["FOMS_ADJUSTED"] = (
138
+ logscale_data[FOMS] - coeffs[0] * logscale_data[TECH] - intercept
139
+ )
140
+ logscale_data["log ENOB"] = np.log(logscale_data[ENOB])
141
+ logscale_data = logscale_data.dropna().reset_index(drop=True)
142
+
143
+ # ENERGY
144
+ foms_max = logscale_data["FOMS_ADJUSTED"].quantile(OUTLIER_THRESHOLD)
145
+ print(f"Maximum FOMS: {foms_max}")
146
+
147
+ residuals, coeffs, intercept = mvgress(
148
+ logscale_data[ENOB], logscale_data["FOMS_ADJUSTED"]
149
+ )
150
+ intercept += pd.Series(residuals).quantile(OUTLIER_THRESHOLD)
151
+ foms_max_by_enob_x = np.arange(math.ceil(np.max(logscale_data[ENOB]) + 1)).reshape(
152
+ -1, 1
153
+ )
154
+ foms_max_by_enob = [float(intercept + coeffs[0] * x) for x in foms_max_by_enob_x]
155
+
156
+ freq_max = np.log(1e10) # logscale_data[FREQ].quantile(TH3)
157
+ fr = logscale_data[FREQ]
158
+ fs = logscale_data["FOMS_ADJUSTED"]
159
+ freq_for_pareto = fr[(fs <= fs.quantile(OUTLIER_THRESHOLD))].reset_index(drop=True)
160
+ foms_for_pareto = fs[(fs <= fs.quantile(OUTLIER_THRESHOLD))].reset_index(drop=True)
161
+ pgroup_freq, pgroup_foms = get_pareto(
162
+ freq_for_pareto,
163
+ foms_for_pareto,
164
+ allow_interior_points=round(len(freq_for_pareto) * 0.05),
165
+ )
166
+ _, foms_coeffs, foms_intercept = mvgress(pgroup_freq, pgroup_foms)
167
+
168
+ def get_max_foms(enob, freq):
169
+ max_by_enob = foms_max_by_enob[round(enob)]
170
+ max_by_freq = foms_coeffs[0] * freq + foms_intercept
171
+ return min(max_by_enob, max_by_freq)
172
+
173
+ max_foms = logscale_data.apply(lambda x: get_max_foms(x[ENOB], x[FREQ]), axis=1)
174
+ pred_energy = foms_sndr2energy(max_foms, bits2sndr(logscale_data[ENOB]))
175
+ error = np.log(np.exp(logscale_data[ENRG]) / pred_energy)
176
+ logscale_data["log ENOB"] = np.log(logscale_data[ENOB])
177
+ tech_energy_residuals, tech_energy_coeffs, tech_energy_intercept = mvgress(
178
+ logscale_data[[TECH, "log ENOB"]], error
179
+ )
180
+ print(
181
+ f"Residuals-tech correlation: {np.corrcoef(logscale_data[TECH], tech_energy_residuals)[0, 1]}"
182
+ )
183
+ print(
184
+ f"Predicted-actual energy correlation: {np.corrcoef(logscale_data[ENRG], np.log(pred_energy))[0, 1]}"
185
+ )
186
+ pred_energy = pred_energy * np.exp(
187
+ tech_energy_intercept
188
+ + tech_energy_coeffs[0] * logscale_data[TECH]
189
+ + tech_energy_coeffs[1] * logscale_data["log ENOB"]
190
+ )
191
+ print(
192
+ f"Predicted-actual energy correlation: {np.corrcoef(logscale_data[ENRG], np.log(pred_energy))[0, 1]}"
193
+ )
194
+ with open("energy_correlation.csv", "w") as f:
195
+ f.write("Actual energy (pJ/op),Predicted energy (pJ/op)\n")
196
+ for i in range(len(pred_energy)):
197
+ f.write(f"{np.exp(logscale_data[ENRG].iloc[i])},{pred_energy.iloc[i]}\n")
198
+ energy_residual = tech_energy_residuals.quantile(1 - OUTLIER_THRESHOLD)
199
+
200
+ freqmodel = {MAX: freq_max}
201
+ fomsmodel = {
202
+ FREQ: foms_coeffs[0],
203
+ MAX: foms_max,
204
+ INTERCEPT: foms_intercept,
205
+ MAX_BY_ENOB: foms_max_by_enob,
206
+ TECH_INTERCEPT: tech_energy_intercept,
207
+ TECH_SLOPE: tech_energy_coeffs[0],
208
+ ENOB_SLOPE: tech_energy_coeffs[1],
209
+ ENRG_RESIDUAL: energy_residual,
210
+ }
211
+ model = {
212
+ FREQ: freqmodel,
213
+ FOMS: fomsmodel,
214
+ COMMENTS: "Tech node, area, and frequency are log-base-e-scaled. "
215
+ "max_by_enob was also considered for limiting the frequency of "
216
+ "ADCs, but max ADC frequency was plenty for realistic PIM "
217
+ "settings at reasonable ADC resolutions.",
218
+ }
219
+ # AREA
220
+ area_data = logscale_data.copy()
221
+ ar_residual, ar_coeffs, ar_intercept = mvgress(
222
+ area_data[AREA_CALCULATE_PARAMS], area_data[AREA]
223
+ )
224
+ ar_intercept -= ar_residual.quantile(1 - AREA_QUANTILE)
225
+
226
+ areamodel = {a: ar_coeffs[i] for i, a in enumerate(AREA_CALCULATE_PARAMS)}
227
+ areamodel["intercept"] = ar_intercept
228
+ model[AREA] = areamodel
229
+
230
+ for k in model:
231
+ if isinstance(model[k], dict):
232
+ m = model[k]
233
+ else:
234
+ m = model
235
+ for k2 in m:
236
+ try:
237
+ m[k2] = float(m[k2])
238
+ except (ValueError, TypeError):
239
+ pass
240
+
241
+ logger.info(f'Writing model to "{output_file}"')
242
+ with open(output_file, "w") as outfile:
243
+ yaml.dump(model, outfile, default_flow_style=False, sort_keys=False)
244
+
245
+ return model
246
+
247
+
248
+ def read_input_data(path: str) -> "pd.DataFrame":
249
+ """Loads input adc_data from path"""
250
+ import pandas as pd
251
+
252
+ if ".xls" == path[-4:] or ".xlsx" == path[-5:]:
253
+ data = pd.read_excel(path)
254
+ else:
255
+ data = pd.read_csv(path)
256
+ return data
257
+
258
+
259
+ if __name__ == "__main__":
260
+ with open("./adc_data/model.yaml", "r") as f:
261
+ model = yaml.safe_load(f)
262
+
263
+ prev = 1
264
+ prev2 = 1
265
+ for r in range(4, 12):
266
+ for f in [1e8, 2.5e8, 5e8, 1e9, 2e9]:
267
+ f_active = f
268
+ params = {FREQ: math.log(f_active), ENOB: r, TECH: math.log(32)}
269
+ e = get_energy(params, model, True)
270
+ params[ENRG] = e
271
+ a = get_area(params, model)
272
+ p = e * f_active
273
+ print(f"{r}, {f_active:.2E}, {a:.2E}")
274
+ prev = e
275
+ prev2 = get_area(params, model)
276
+
277
+ resolutions = [x for x in range(4, 12)]
278
+ frequencies = [1e7] + [5e7] + [1e8 * x for x in range(1, 21)]
279
+ print("," + ",".join([f"{x/1e6}" for x in frequencies]))
280
+ for r in resolutions:
281
+ energies = []
282
+ areas = []
283
+ for f in frequencies:
284
+ params = {FREQ: math.log(f), ENOB: r, TECH: math.log(32)}
285
+ params[ENRG] = math.log(get_energy(params, model, True) * 1e12)
286
+ energies.append(math.exp(params[ENRG]))
287
+ areas.append(get_area(params, model))
288
+
289
+ print(f"{r}," + ",".join([f"{e:.2E}" for e in areas]))
@@ -0,0 +1,53 @@
1
+ """
2
+ This script downloads Boris Murmann's ADC survey and packages it into a .CSV
3
+ ADC list for user use.
4
+ """
5
+
6
+ # Data source:
7
+ # B. Murmann, "ADC Performance Survey 1997-2023," [Online].
8
+ # Available: https://github.com/bmurmann/ADC-survey.
9
+
10
+ import logging
11
+
12
+ import pandas as pd
13
+
14
+ from hwcomponents_adc.headers import *
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ XLS_FILE = "adc_data/ADC-survey/xls/ADCsurvey_latest.xls"
20
+
21
+
22
+ def get_csv(outfile: str):
23
+ """Converts survey adc_data to a CSV file"""
24
+ xls_path = os.path.join(os.path.dirname(__file__), XLS_FILE)
25
+ if not os.path.exists(xls_path):
26
+ xls_path += "x"
27
+ isscc = pd.read_excel(xls_path, sheet_name="ISSCC")
28
+ vsli = pd.read_excel(xls_path, sheet_name="VLSI")
29
+ xls = pd.concat([isscc, vsli])
30
+
31
+ csv = pd.DataFrame()
32
+ numeric_cols = [
33
+ "fs [Hz]",
34
+ "AREA [mm^2]",
35
+ "TECHNOLOGY",
36
+ "P [W]",
37
+ "P/fsnyq [pJ]",
38
+ FOMS,
39
+ SNDR,
40
+ ]
41
+ for c in numeric_cols:
42
+ xls = xls[pd.to_numeric(xls[c], errors="coerce").notnull()]
43
+ csv[FREQ] = xls["fs [Hz]"]
44
+ csv[TECH] = xls["TECHNOLOGY"] * 10**3 # um >> nm
45
+ csv[AREA] = xls["AREA [mm^2]"] * 10**6 # mm^2 -> um^2
46
+ csv[ENRG] = xls["P/fsnyq [pJ]"]
47
+ csv[SNDR] = xls[SNDR]
48
+ csv[ENOB] = xls[SNDR].apply(sndr2bits)
49
+ csv[FOMS] = xls[FOMS]
50
+ csv.reset_index(inplace=True, drop=True)
51
+ if os.path.exists(outfile):
52
+ os.remove(outfile)
53
+ csv.to_csv(outfile)
@@ -0,0 +1,104 @@
1
+ import logging
2
+ from typing import Dict
3
+ from hwcomponents_adc.headers import *
4
+ from .model import get_energy, get_area
5
+ import math
6
+ import logging
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class ADCRequest:
12
+ def __init__(
13
+ self,
14
+ bits: float, # Resolution (bits)
15
+ tech: float, # Tech node (nm)
16
+ throughput: float = 0, # ops/channel/second
17
+ n_adcs: int = 1, # Number of ADCs. Fractions allowed
18
+ logger: logging.Logger = None,
19
+ ):
20
+ self.bits = bits
21
+ self.tech = tech
22
+ self.throughput = throughput
23
+ self.n_adcs = n_adcs
24
+ self.logger = logger
25
+ assert self.bits >= 4, "Resolution must be >= 4 bits"
26
+
27
+ def energy_per_op(self, model: Dict) -> float:
28
+ """Returns energy per operation in Joules."""
29
+ design_params = {
30
+ ENOB: self.bits,
31
+ TECH: math.log(self.tech),
32
+ FREQ: math.log(self.throughput / self.n_adcs),
33
+ }
34
+ e_per_op = get_energy(design_params, model, True)
35
+
36
+ self.logger.info("\tAlternative designs:")
37
+ for n_adcs in range(max(self.n_adcs - 5, 1), self.n_adcs + 5):
38
+ f = self.throughput / n_adcs
39
+ design_params[FREQ] = math.log(f)
40
+ try:
41
+ e = get_energy(design_params, model, True)
42
+ a = self.area(model, n_adcs)
43
+ l = "\tCHOSEN > " if n_adcs == self.n_adcs else "\t "
44
+ self.logger.info(
45
+ f"{l}{n_adcs:2f} ADCs running at {f:2E}Hz: "
46
+ f"{e:2E}pJ/op, {a/1e6:2E}mm^2"
47
+ )
48
+ except AssertionError:
49
+ pass
50
+ self.logger.info("")
51
+ return e_per_op
52
+
53
+ def area(self, model: Dict, n_adc_override=-1) -> float:
54
+ """Returns area in um^2."""
55
+ n_adcs = self.n_adcs if n_adc_override == -1 else n_adc_override
56
+ design_params = {
57
+ ENOB: self.bits,
58
+ TECH: math.log(self.tech),
59
+ FREQ: math.log(self.throughput / n_adcs),
60
+ }
61
+ design_params[ENRG] = math.log(get_energy(design_params, model, True))
62
+ return get_area(design_params, model) * n_adcs
63
+
64
+
65
+ CACHED_MODEL = None
66
+
67
+
68
+ def quick_get_area(
69
+ bits: float, tech: float, throughput: float, n_adcs: int, energy=None
70
+ ):
71
+ """Returns area in um^2. For testing purposes."""
72
+ global CACHED_MODEL
73
+ if CACHED_MODEL is None:
74
+ import main
75
+ import yaml
76
+
77
+ CACHED_MODEL = yaml.load(open(main.MODEL_FILE).read(), Loader=yaml.SafeLoader)
78
+ design_params = {
79
+ ENOB: bits,
80
+ TECH: math.log(tech),
81
+ FREQ: math.log(throughput / n_adcs),
82
+ }
83
+ design_params[ENRG] = math.log(
84
+ energy or get_energy(design_params, CACHED_MODEL, True)
85
+ )
86
+ return get_area(design_params, CACHED_MODEL) * n_adcs
87
+
88
+
89
+ def quick_get_energy(
90
+ bits: float, tech: float, throughput: float, n_adcs: int, energy=None
91
+ ):
92
+ """Returns area in um^2. For testing purposes."""
93
+ global CACHED_MODEL
94
+ if CACHED_MODEL is None:
95
+ import main
96
+ import yaml
97
+
98
+ CACHED_MODEL = yaml.load(open(main.MODEL_FILE).read(), Loader=yaml.SafeLoader)
99
+ design_params = {
100
+ ENOB: bits,
101
+ TECH: math.log(tech),
102
+ FREQ: math.log(throughput / n_adcs),
103
+ }
104
+ return get_energy(design_params, CACHED_MODEL)
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import murmannsurvey
4
+ import model
5
+ from hwcomponents_adc.headers import *
6
+
7
+ if __name__ == "__main__":
8
+ murmannsurvey.get_csv(ADC_LIST_DEFAULT)
9
+ data = model.read_input_data(ADC_LIST_DEFAULT)
10
+ model.build_model(data, MODEL_DEFAULT, False)
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: hwcomponents-adc
3
+ Version: 0.1
4
+ Summary: A package for estimating the energy and area of Analog-Digital Converters
5
+ Author-email: Tanner Andrulis <Andrulis@mit.edu>
6
+ License: MIT
7
+ Keywords: accelerator,hardware,energy,estimation,analog,adc
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
12
+ Requires-Python: >=3.12
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: PyYAML
15
+ Requires-Dist: numpy
16
+ Requires-Dist: pandas
17
+
18
+ # HWComponents-ADC
19
+ HWComponents-ADC models the area and energy of Analog-Digital Converters (ADCs) for use
20
+ in analog & mixed-signal accelerator designs.
21
+
22
+ Models are based on statistical analysis of published ADC performance data in Boris
23
+ Murmann's ADC Performance Survey [1]. The energy model is based on the observation that
24
+ the maximum efficiency of an ADC is bounded by the sampling rate and the resolution [1],
25
+ and the area model is based on regression analysis. Estimations are optimistic; they
26
+ answer the question "what is the best possible ADC design for the given parameters?".
27
+
28
+ ## Installation
29
+ Clone the repository and install with pip:
30
+
31
+ ```bash
32
+ git clone https://github.com/Accelergy-Project/hwcomponents-adc.git
33
+ cd hwcomponents-adc
34
+ pip install .
35
+
36
+ # Check that the installation is successful
37
+ hwc --list | grep ADC
38
+ ```
39
+
40
+ ## Usage
41
+ ### Inerface
42
+
43
+ This model introduces the ADC model. ADC models can be instantiated with the
44
+ following parameters:
45
+ - `n_bits`: the resolution of the ADC
46
+ - `tech_node`: the technology node in meters
47
+ - `n_adcs`: the number of ADCs working together, in the case of alternating
48
+ ADCs
49
+ - `throughput`: the aggregate throughput of the ADCs, in samples per second
50
+
51
+ ADCs support the following actions:
52
+ - `read` or `convert`: Convert a single value from analog to digital. Note: if
53
+ there are multiple ADCs, this is a single conversion from a single ADC.
54
+
55
+ ### Exploring Tradeoffs
56
+ There are several tradeoffs available around ADC design:
57
+ - Lower-resolution ADCs are smaller and more energy-efficient.
58
+ - Using more ADCs in parallel allows for a lower frequency, but increases the
59
+ area.
60
+ - Using fewer ADCs in parallel allows for a higher frequency. Up to a point,
61
+ this will not increase the area or energy/area of the ADCs. However, at some
62
+ this will result in an exponential increase in energy/area.
63
+ - Lower-resolution ADCs can run at higher frequencies before the exponential
64
+ increase in energy/area occurs.
65
+
66
+ When the HWComponents-ADC runs, it will output a list of alternative design options.
67
+ Each will report a number of ADCs and frequency needed to achieve the desired
68
+ throughput, as well as the area and energy of the ADCs. You can then use this
69
+ information to make tradeoffs between ADC resolution, frequency, and number of
70
+ ADCs.
71
+
72
+ HWComponents-ADC is the work of Tanner Andrulis & Ruicong Chen.
73
+
74
+ ## Updating the ADC Model
75
+ The generated ADC model is based on the data in Boris Murmann's survey [1],
76
+ included in the submodule. This survey is updated periodically. The model can
77
+ be update to reflect the most recent data by running the following:
78
+
79
+ ```bash
80
+ pip3 install scikit-learn
81
+ pip3 install pandas
82
+ pip3 install numpy
83
+ git submodule update --init --recursive --remote
84
+ python3 update_model.py
85
+ ```
86
+
87
+ This is only necessary if more recent data is published. If the data here is
88
+ out of date, please open an issue or pull request.
89
+
90
+ ## References
91
+ [1] B. Murmann, "ADC Performance Survey 1997-2023," [Online]. Available:
92
+ https://github.com/bmurmann/ADC-survey
93
+
94
+ ## License
95
+ This work is licensed under the MIT license. See license.txt for details.
96
+
97
+ ## Citing HWComponents-ADC
98
+ If you use this model in your work, please cite the following:
99
+
100
+ ```bibtex
101
+ @misc{andrulis2024modelinganalogdigitalconverterenergyarea,
102
+ title={Modeling Analog-Digital-Converter Energy and Area for Compute-In-Memory Accelerator Design},
103
+ author={Tanner Andrulis and Ruicong Chen and Hae-Seung Lee and Joel S. Emer and Vivienne Sze},
104
+ year={2024},
105
+ eprint={2404.06553},
106
+ archivePrefix={arXiv},
107
+ primaryClass={cs.AR},
108
+ url={https://arxiv.org/abs/2404.06553},
109
+ }
110
+ ```
@@ -0,0 +1,15 @@
1
+ README.md
2
+ pyproject.toml
3
+ hwcomponents_adc/__init__.py
4
+ hwcomponents_adc/headers.py
5
+ hwcomponents_adc/main.py
6
+ hwcomponents_adc/model.py
7
+ hwcomponents_adc/murmannsurvey.py
8
+ hwcomponents_adc/optimizer.py
9
+ hwcomponents_adc/update_model.py
10
+ hwcomponents_adc.egg-info/PKG-INFO
11
+ hwcomponents_adc.egg-info/SOURCES.txt
12
+ hwcomponents_adc.egg-info/dependency_links.txt
13
+ hwcomponents_adc.egg-info/requires.txt
14
+ hwcomponents_adc.egg-info/top_level.txt
15
+ hwcomponents_adc/adc_data/model.yaml
@@ -0,0 +1,3 @@
1
+ PyYAML
2
+ numpy
3
+ pandas
@@ -0,0 +1,3 @@
1
+ build
2
+ dist
3
+ hwcomponents_adc
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "hwcomponents-adc"
7
+ version = "0.1"
8
+ description = "A package for estimating the energy and area of Analog-Digital Converters"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ license = {text = "MIT"}
12
+ authors = [
13
+ {name = "Tanner Andrulis", email = "Andrulis@mit.edu"}
14
+ ]
15
+ keywords = ["accelerator", "hardware", "energy", "estimation", "analog", "adc"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)",
21
+ ]
22
+ dependencies = [
23
+ "PyYAML",
24
+ "numpy",
25
+ "pandas",
26
+ ]
27
+
28
+ [tool.setuptools]
29
+ packages = {find = {}}
30
+ include-package-data = true
31
+ py-modules = ["hwcomponents_adc"]
32
+
33
+ [tool.setuptools.package-data]
34
+ hwcomponents_adc = [
35
+ "adc_data/model.yaml",
36
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+