mirror of
https://github.com/lleene/hugo-site.git
synced 2025-01-23 20:12:21 +01:00
272 lines
15 KiB
Markdown
272 lines
15 KiB
Markdown
|
---
|
|||
|
title: "Synthesizing Sinusoids"
|
|||
|
date: 2022-05-17T13:17:04+02:00
|
|||
|
draft: false
|
|||
|
toc: true
|
|||
|
math: true
|
|||
|
tags:
|
|||
|
- signal-processing
|
|||
|
- delta-sigma-modulation
|
|||
|
- digital-circuits
|
|||
|
- python
|
|||
|
---
|
|||
|
|
|||
|
Here I will go over a hardware efficient digital-technique for synthesizing a
|
|||
|
high-fidelity sinusoidal tone for self-test and electrical characterization
|
|||
|
purposes. This will be a application of several state-of-the-art hardware
|
|||
|
techniques to minimize hardware complexity while readily
|
|||
|
generating a precise tone with well over 100 dB of dynamic range. Further more
|
|||
|
the resulting output bit-stream is delta-sigma modulated enabling the use of a
|
|||
|
low-complexity 4 bit digital-to-analogue-converter that employs
|
|||
|
dynamic-element-matching.
|
|||
|
|
|||
|
## Synthesizer
|
|||
|
|
|||
|
``` goat
|
|||
|
+-----------------------+ 16b +--------------+ 16b +-------------+ 4b
|
|||
|
| 32 bit Recursive | / | 1:16 | / | 3rd Order | /
|
|||
|
| Discrete-Time +---+-->| Rotated CIC +---+-->| Delta-Sigma +---+--> Output Bitstream
|
|||
|
| Sinusoidal Oscillator | / | Interpolator | / | Modulator | /
|
|||
|
+-----------------------+ +--------------+ +-------------+
|
|||
|
```
|
|||
|
|
|||
|
The overall system composition is illustrated above and consists of three
|
|||
|
modules. The first module is a recursive digital oscillator and operates at a
|
|||
|
higher precision but lower clock rate to generate the target test-tone. The
|
|||
|
proceeding modules encode this high-resolution digital signal into a low
|
|||
|
resolution digital bit-stream there the quantization noise is shaped towards
|
|||
|
the high-frequency band that can then be filtered out in the analogue-domain.
|
|||
|
|
|||
|
## Digital Oscillation
|
|||
|
|
|||
|
There are numerous all-digital methods for synthesizing a sinusoidal signal
|
|||
|
precisely. The most challenging aspect here is the trigonometric functions that
|
|||
|
are difficult compute given limited hardware resource. A common approach to
|
|||
|
avoid this is to use a look-up table representing `cos(x)` for mapping phase to
|
|||
|
amplitude but this generally requires a significant amount of memory.
|
|||
|
Alternatively a recursive feedback mechanism can be used that will oscillate
|
|||
|
with a known frequency and amplitude given a set of parameters. The later
|
|||
|
approach has negligible memory requirements but instead requires full-precision
|
|||
|
multiplication. However considering that we are required to perform delta-sigma
|
|||
|
encoding at the output this feedback mechanism can run at a reduced clock-rate
|
|||
|
allowing this multiplication to be performed in a pipelined fashion which is
|
|||
|
considerably more affordable.
|
|||
|
|
|||
|
``` goat
|
|||
|
- .-. .-.
|
|||
|
.->| Σ +------*----->| Σ +---> Digital Sinusoid
|
|||
|
| '-' | '-'
|
|||
|
| ^ v ^
|
|||
|
| | .-----. |
|
|||
|
| | | z⁻¹ | '----- Offset
|
|||
|
| | . '--+--'
|
|||
|
| | /| |
|
|||
|
| '+ |<---*
|
|||
|
| K \| |
|
|||
|
| ' v
|
|||
|
| .-----.
|
|||
|
'----------+ z⁻¹ |
|
|||
|
'-----'
|
|||
|
```
|
|||
|
|
|||
|
The biquad feedback configuration shown above is one of several oscillating
|
|||
|
structures presented in [^1] with the equivalent a python model presented below.
|
|||
|
The idea here is to perform full-precision synthesis at 64 or 32 bit with a
|
|||
|
pipelined multiplier such that this loop runs at 1/M times the modulator clock
|
|||
|
speed where M is the oversampling ratio that is chosen to optimize the
|
|||
|
multiplier pipeline. In this case M=16 and we will be using 32 bit frequency
|
|||
|
precision.
|
|||
|
|
|||
|
``` python3
|
|||
|
class Resonator:
|
|||
|
def __init__(self, frequency: float = 0.1, amplitude: float = 0.5):
|
|||
|
K = 2 * np.cos(2 * np.pi * frequency)
|
|||
|
self.A = 0.0
|
|||
|
self.B = amplitude * np.sqrt(2.0 - K)
|
|||
|
self.K = K
|
|||
|
def update(self) -> int:
|
|||
|
self.A, self.B = (self.A * self.K - self.B, self.A)
|
|||
|
return self.A
|
|||
|
```
|
|||
|
|
|||
|
The coefficient K determines the frequency of oscillation as a ratio relative to
|
|||
|
the operating clock speed. Using \\( K = 2 cos( 2 \pi freq )\\) such that the
|
|||
|
oscillation occurs as \\( freq \cdot fclk \\). The initial condition of the two
|
|||
|
registers will determine the oscillation amplitude. Setting the first register
|
|||
|
to zero and the second to \\( A \sqrt{2 - K} \\) will yield amplitude of \\(A\\)
|
|||
|
around zero. We can then offset this signal to specify the level around which
|
|||
|
tone oscillates.
|
|||
|
|
|||
|
## Band-Select Interpolation
|
|||
|
|
|||
|
The main drawback of not synthesizing the sinusoid at a fractional clock rate
|
|||
|
is that we must take care of the aliased components when we increase the
|
|||
|
data-rate. Fortunately there are a family of filters that are extremely
|
|||
|
efficient at up-sampling a signal while rejecting the aliasing components known
|
|||
|
as cascaded integrator-comb filters (CIC)[^2]. These filters consist of several
|
|||
|
simple accumulators and differentiators that can be configured to reject
|
|||
|
aliasing components.
|
|||
|
|
|||
|
$$ H(z) = \left( \frac{ 1 - z^{-M} }{ 1 - z^{-1} } \right)^N $$
|
|||
|
|
|||
|
The transfer function of such a filter is formulated above. This shows that a
|
|||
|
CIC structures of order \\( N \\) operating at a oversampling ratio
|
|||
|
\\( M \\) will distribute M zeros uniformly around the unit circle. This
|
|||
|
completely removes any DC components that end up at the aliasing tones at
|
|||
|
multiples of \\( fclk / M\\). However we know priori that we will introduce
|
|||
|
aliasing components at integer multiples of the input tone when up-sampling:
|
|||
|
\\( freq \cdot fclk / M \\). Making a slight modification to this structure
|
|||
|
as discussed in [^3] allows us to further optimize a second-order CIC filter to
|
|||
|
specifically reject these components instead.
|
|||
|
|
|||
|
$$ H(z) = \frac{ 1 - K \cdot z^{-M} + z^{-2M} }{ 1 - K_M z^{-1} + z^{-2} } $$
|
|||
|
|
|||
|
Notice that the coefficient K from the resonator structure is reused here and
|
|||
|
we introduce a new scaling coefficient \\(K_M = 2 \cdot 2 * cos(2 \pi * freq / M )\\)
|
|||
|
which we will approximate by tailor expansion to avoid the multiplication
|
|||
|
requirement as this factor does not require high precision. Again a python
|
|||
|
implementation is shown below for reference.
|
|||
|
|
|||
|
``` python3
|
|||
|
class Interpolator:
|
|||
|
def __init__(self, frequency: float = 0.1, osr: int = 32):
|
|||
|
K = 2 * np.cos(2 * np.pi * frequency)
|
|||
|
KM = 2 * np.cos(2 * np.pi * frequency / osr )
|
|||
|
self.fir_coef = np.array([1, -K, 1]) # FIR coefficients
|
|||
|
self.irr_coef = np.array([-KM, 1]) # IRR coefficients
|
|||
|
self.comb_integrator = np.zeros((2,), dtype=float)
|
|||
|
self.comb_decimator = np.zeros((3,), dtype=float)
|
|||
|
self.osr = osr
|
|||
|
self.count = 0
|
|||
|
|
|||
|
def update(self, new_val: float) -> float:
|
|||
|
self.comb_integrator = np.append(
|
|||
|
np.dot(self.fir_coef, self.comb_decimator)
|
|||
|
+ np.dot(-self.irr_coef, self.comb_integrator),
|
|||
|
self.comb_integrator[:-1],
|
|||
|
)
|
|||
|
if self.count == 0:
|
|||
|
self.comb_decimator = np.append(new_val, self.comb_decimator[1:])
|
|||
|
self.count = (self.count + 1) % self.osr
|
|||
|
return self.comb_integrator[0]
|
|||
|
```
|
|||
|
|
|||
|
Combing the two feedback mechanisms we can construct a second-order CIC based
|
|||
|
digital resonator with a interpolated output that fully rejects aliasing
|
|||
|
components. This configuration is shown below. Now let us use Taylor
|
|||
|
approximation to resolve the coefficient KM such that it is reduced to
|
|||
|
two-component addition. The first two non-zero coefficients for cos are
|
|||
|
\\( cos(x) = 1 - x^2 / 2 \\). Hence we can approximate as follows
|
|||
|
\\( KM = 2 - 1 >> \lfloor 2 \log_2( 2 \pi * freq / M ) \rfloor \\) where
|
|||
|
\\(>>\\) is the binary shift-left operator.
|
|||
|
|
|||
|
``` goat
|
|||
|
Fractional Clock Rate <+ +> Full Clock Rate
|
|||
|
- .-. .-. .-.
|
|||
|
.->| Σ +------*---------->| Σ +----->| Σ +-------*-------> Interpolated Sinusoid
|
|||
|
| '-' | '-' '-' |
|
|||
|
| ^ v - ^ ^ - ^ ^ . v
|
|||
|
| | .-----. / | / | /| .--+--.
|
|||
|
| | | z⁻ᴹ | | | | '+ |<-+ z⁻¹ |
|
|||
|
| | . '--+--' . | | | KM \| '--+--'
|
|||
|
| | /| | |\ | | | ' |
|
|||
|
| '+ |<---*--->| +-' | | v
|
|||
|
| K \| | |/ K | | .--+--.
|
|||
|
| ' v ' | '---------+ z⁻¹ |
|
|||
|
| .-----. | '-----'
|
|||
|
'----------+ z⁻ᴹ +---------'
|
|||
|
'-----'
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
The resulting configuration only requires one multiplication to be computed at
|
|||
|
a fractional clock-rate. Note that practically a hardware implementation will
|
|||
|
stagger the computation in time for each of the processing stages.
|
|||
|
|
|||
|
## Sigma-Delta Modulation
|
|||
|
|
|||
|
The purpose of digital sigma-delta modulation is primarily to reduce the
|
|||
|
hardware requirements for signal-processing in the analogue-domain. Digitizing
|
|||
|
a high resolution 16 bit signal is exceedingly expensive once we consider
|
|||
|
component variation requirements if we want to preserve the fidelity of our
|
|||
|
signal. The main idea here is the reduce the resolution of the output bitstream
|
|||
|
while modulating the quantization noise such that accuracy is preserved in the
|
|||
|
lower frequencies while noise due to the truncation of the digital bits is only
|
|||
|
present at higher frequencies. This allows us to use a low resolution
|
|||
|
digital-to-analogue converter that employs mismatch cancellation techniques
|
|||
|
at low cost to further remove the impact of component imperfection from
|
|||
|
corrupting the precision in-band.
|
|||
|
|
|||
|
A popular approach here is the use of multistage noise-shaping modulator
|
|||
|
topologies. Here we will employ a special maximum-sequence-length configuration
|
|||
|
from [^4] which avoids any unwanted periodicity commonly found in the output
|
|||
|
of conventional modulators when processing certain static signals. A python
|
|||
|
realization of this modulation process is shown below in the case of a first
|
|||
|
order modulator.
|
|||
|
|
|||
|
``` python3
|
|||
|
class Modulator:
|
|||
|
def __init__(self, resolution: int = 16, coupling: int = 0) -> None:
|
|||
|
self.acc = 0
|
|||
|
self.coupling = coupling
|
|||
|
self.resolution = resolution
|
|||
|
|
|||
|
def update(self, new_val: int) -> bool:
|
|||
|
last_val = self.acc & 1
|
|||
|
pre_calc = self.acc + new_val + (self.coupling if last_val else 0)
|
|||
|
self.acc = pre_calc % (2 ** self.resolution)
|
|||
|
return last_val
|
|||
|
```
|
|||
|
|
|||
|
The third-order configuration of the modulator is shown below. Here the Nx[n]
|
|||
|
components represent the coupling factor α and simply use the Cx[n-1] bitstream
|
|||
|
from the last cycle. This factor is a small integer chosen such that
|
|||
|
2^N-α is a prime number given a fixed modulator resolution N.
|
|||
|
|
|||
|
``` goat
|
|||
|
. C1[n] .-.
|
|||
|
D[n] |\ .-------------------------------->| Σ +--> Q[n]
|
|||
|
--->+ + '-'
|
|||
|
| \ ^ ^
|
|||
|
\ | . C2[n] .-------. / |
|
|||
|
N1[n]| | S1[n] |\ .---->+ 1-z⁻¹ +-----' |
|
|||
|
--->+ +--*--->+ + '-------' |
|
|||
|
| | | | \ |
|
|||
|
/ | | \ | . C3[n] .-----+----.
|
|||
|
| / | N2[n]| | S2[n] |\ .---->+ (1-z⁻¹)² |
|
|||
|
.->+ / | --->+ +--*--->+ + '----------'
|
|||
|
| |/ | | | | | \
|
|||
|
| ' | / | | \ |
|
|||
|
| .-----. | | / | N3[n]| | S3[n]
|
|||
|
'-+ z⁻¹ +' .->+ / | --->+ +-.
|
|||
|
'-----' | |/ | | | |
|
|||
|
| ' | / | |
|
|||
|
| .-----. | | / |
|
|||
|
'-+ z⁻¹ +' .->+ / |
|
|||
|
'-----' | |/ |
|
|||
|
| ' |
|
|||
|
| .-----. |
|
|||
|
'-+ z⁻¹ +'
|
|||
|
'-----'
|
|||
|
```
|
|||
|
|
|||
|
The output Q[n] will represent a multi-bit quantization result that increases in
|
|||
|
bit-depth as the modulator order increases as the derivative components of CX[n]
|
|||
|
increase in dynamic range for higher order derivatives. This has a rather
|
|||
|
unfortunate side-effect that the signal dynamic range is only a fraction of the
|
|||
|
total output dynamic range in this case 1/8. Fortunately these components are
|
|||
|
exclusively high-frequency and so including a 3-tap Bartlett-Window FIR a the
|
|||
|
output alleviates this problem by amplifying the signal-band and rejecting
|
|||
|
the quantization-noise. In that scenario the signal dynamic range uses a little
|
|||
|
under half the full dynamic range of the signal seen at the output.
|
|||
|
|
|||
|
## References:
|
|||
|
|
|||
|
[^1]: C. S. Turner, ''Recursive discrete-time sinusoidal oscillators,'' IEEE Signal Process. Mag, vol. 20, no. 3, pp. 103-111, May 2003. [Online]: http://dx.doi.org/10.1109/MSP.2003.1203213.
|
|||
|
|
|||
|
[^2]: E. Hogenauer, ''An economical class of digital filters for decimation and interpolation,'' IEEE Trans. Acoust., Speech, Signal Process., vol. 29, no. 2, pp. 155-162, April 1981. [Online]: http://dx.doi.org/10.1109/TASSP.1981.1163535.
|
|||
|
|
|||
|
[^3]: L. Lo Presti, ''Efficient modified-sinc filters for sigma-delta A/D converters,'' IEEE Trans. Circuits Syst. II, vol. 47, no. 11, pp. 1204-1213, Nov. 2000. [Online]: http://dx.doi.org/10.1109/82.885128.
|
|||
|
|
|||
|
[^4]: K. Hosseini and M. P. Kennedy, ''Maximum Sequence Length MASH Digital Delta–Sigma Modulators,'' IEEE Trans. Circuits Syst. I, vol. 54, no. 12, pp. 2628-2638, Dec. 2007. [Online]: http://dx.doi.org/10.1109/TCSI.2007.905653.
|