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.
- hwcomponents_adc-0.1/PKG-INFO +110 -0
- hwcomponents_adc-0.1/README.md +93 -0
- hwcomponents_adc-0.1/hwcomponents_adc/__init__.py +1 -0
- hwcomponents_adc-0.1/hwcomponents_adc/adc_data/model.yaml +40 -0
- hwcomponents_adc-0.1/hwcomponents_adc/headers.py +68 -0
- hwcomponents_adc-0.1/hwcomponents_adc/main.py +222 -0
- hwcomponents_adc-0.1/hwcomponents_adc/model.py +289 -0
- hwcomponents_adc-0.1/hwcomponents_adc/murmannsurvey.py +53 -0
- hwcomponents_adc-0.1/hwcomponents_adc/optimizer.py +104 -0
- hwcomponents_adc-0.1/hwcomponents_adc/update_model.py +10 -0
- hwcomponents_adc-0.1/hwcomponents_adc.egg-info/PKG-INFO +110 -0
- hwcomponents_adc-0.1/hwcomponents_adc.egg-info/SOURCES.txt +15 -0
- hwcomponents_adc-0.1/hwcomponents_adc.egg-info/dependency_links.txt +1 -0
- hwcomponents_adc-0.1/hwcomponents_adc.egg-info/requires.txt +3 -0
- hwcomponents_adc-0.1/hwcomponents_adc.egg-info/top_level.txt +3 -0
- hwcomponents_adc-0.1/pyproject.toml +36 -0
- hwcomponents_adc-0.1/setup.cfg +4 -0
|
@@ -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 @@
|
|
|
1
|
+
|
|
@@ -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
|
+
]
|