Skip to content

Refactor and add ADC model capability #104

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Apr 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
matrix:
platform:
- ubuntu-latest
python-version: ['3.9', '3.10', '3.11', '3.12']
python-version: ['3.10', '3.11', '3.12']

steps:
- uses: actions/checkout@v3
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ python_adc_eval.egg-info
dist
.ruff_cache
.coverage
.secret
build
3 changes: 2 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ Given an array of values representing the output of an ADC, the spectrum can be
leak=<adjacent bins to filter>,
window=<window type (rectangular/hanning)>,
no_plot=<True/False>,
yaxis=<"power"/"fullscale">
yaxis=<"power"/"fullscale"/"magnitude">,
single_sided=<True/False>
)
Expand Down
4 changes: 0 additions & 4 deletions adc_eval/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
"""Initialization file for module."""

from . import spectrum
from . import converters
from . import signals
1 change: 1 addition & 0 deletions adc_eval/adcs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Initialization file for module."""
154 changes: 154 additions & 0 deletions adc_eval/adcs/basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
"""Basic ADC models."""

import numpy as np


def dac(samples, nbits=8, vref=1):
"""Digital to analog converter."""
quants = 2**nbits
dv = vref / quants
return samples * dv


class ADC:
"""
Generic ADC Class.

...

Parameters
----------
nbits : int, default=8
Number of bits for the ADC.
fs : int or float, default=1
Sample rate for the ADC in Hz.
vref : int or float, default=1
Reference level of the ADC in Volts ([0, +vref] conversion range).
seed : int, default=1
Seed for random variable generation.
**kwargs
Extra arguments.

Attributes
-------
vin : float
Sets or returns the current input voltage level. Assumed +/-vref/2 input
vlsb : float
LSB voltage of the converter. vref/2^nbits
noise : float, default=0
Sets or returns the stdev of the noise generated by the converter.
mismatch : float, default=0
Sets or returns the stdev of the mismatch of the converter.
offset : tuple of float, default=(0, 0)
Sets the (mean, stdev) of the offset of the converter.
gain_error : tuple of float, default=(0, 0)
Sets the (mean, stdev) of the gain error of the converter.
distortion : list of float, default=[1]
Sets the harmonic distortion values with index=0 corresponding to HD1.
Example: For unity gain and only -30dB of HD3, input is [1, 0, 0.032]
dout : int
Digital output code for current vin value.

Methods
-------
run_step

"""

def __init__(self, nbits=8, fs=1, vref=1, seed=1, **kwargs):
"""Initialization function for Generic ADC."""
np.random.seed(seed)
self.nbits = nbits
self.fs = fs
self.vref = vref
self.seed = seed
self.err = {"noise": 0, "gain": 0, "dist": [1], "offset": 0, "mismatch": 0}
self.dbits = np.zeros(nbits)
self.dval = 0

@property
def vin(self):
"""Return input value."""
return self._vin

@vin.setter
def vin(self, x):
"""Set the input value."""
x += self.vref / 2
x = max(0, min(x, self.vref))
self._vin = x

@property
def vlsb(self):
"""Return the LSB voltage."""
return self.vref / 2**self.nbits

@property
def noise(self):
"""Return noise status."""
return self.err["noise"]

@noise.setter
def noise(self, stdev):
"""Set noise stdev in Vrms."""
self.err["noise"] = stdev

@property
def mismatch(self):
"""Return noise stdev."""
print("WARNING: 'mismatch' feature not implemented for this class.")
return False

@mismatch.setter
def mismatch(self, stdev):
"""Set mismatch stdev."""
print("WARNING: 'mismatch' feature not implemented for this class.")
pass

@property
def offset(self):
"""Return offset value."""
return self.err["offset"]

@offset.setter
def offset(self, values):
"""Set offset mean and stdev."""
self.err["offset"] = values[0] + values[1] * np.random.randn(1)

@property
def gain_error(self):
"""Return gain error status."""
return self.err["gain"]

@gain_error.setter
def gain_error(self, values):
"""Set gain error mean and stdev."""
self.err["gain"] = values[0] + values[1] * np.random.randn(1)

@property
def distortion(self):
"""Return distortion gains (1st-order indexed)."""
return self.err["dist"]

@distortion.setter
def distortion(self, gains):
"""Set distortion gains (1st-order indexed)."""
self.err["dist"] = gains

@property
def dout(self):
"""Return digital output code."""
return int(self.dval)

def run_step(self):
"""Run a single ADC step."""
vinx = self.vin
dval = int(
min(max(int((2**self.nbits) * vinx / self.vref), 0), 2**self.nbits - 1)
)
bits = [int(x) for x in bin(dval)[2:]]

while len(bits) < self.nbits:
bits.insert(0, 0)
self.dbits = bits
self.dval = dval
29 changes: 0 additions & 29 deletions adc_eval/converters.py

This file was deleted.

49 changes: 49 additions & 0 deletions adc_eval/eval/simulate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""Generic simulator class for adc evaluation."""

import numpy as np
from tqdm import tqdm


class Simulator:
"""Class for handling simulation functions."""

def __init__(self, adc_obj, xarray):
"""Initialize the simulator class."""
self.dval = []
self.adc = adc_obj
self.vin = self.calc_error(xarray)

@property
def out(self):
"""Return output value array."""
return np.array(self.dval)

def calc_error(self, vin):
"""Using the adc obj, calculates global signal error."""
vinx = vin

# First calculate gain error
vinx *= (1 + self.adc.err["gain"]) * self.adc.err["dist"][0]

# Next calculate the harmonic distortion
for index, gain in enumerate(self.adc.err["dist"]):
if index > 0:
vinx += gain * vin ** (index + 1)

# Now add the offset
vinx += self.adc.err["offset"]

# Now add random noise
vinx += self.adc.err["noise"] * np.random.randn(vin.size)

return vinx

def run(self):
with tqdm(
range(len(self.vin)), "RUNNING", unit=" samples", position=0, leave=True
) as pbar:
for xval in self.vin:
self.adc.vin = xval
self.adc.run_step()
self.dval.append(self.adc.dout)
pbar.update()
Loading