I want to generate linear frequency modulated (LFM) waveform or a chirp signal using DDS in verilog. I am not using DDS IP Core. I have created DDS module using 8-bit rate counter, a 12-bit address counter, and an 8-bit by 4096-line sine RAM to produce sinusoidal signal. I am stuck with how to incorporate the chirp rate and go further.
2 Answers
What you are creating is typically called a "Numerically Controlled Oscillator" or NCO, and when integrated with a D/A Converter would form a Direct Digital Synthesizer (DDS), but these are often interchanged such that an NCO alone is referred to by some as a DDS.
You need to include a "Frequency Control Word" as the input to your counter (accumulator) which essentially sets the step size. If you put in a "1", then your counter should increment by 1 which is its slowest rate, and should wrap on overflow. One cycle until wrapping will represent one cycle of your sinusoid, and thus produce the slowest frequency. If you instead entered a "2" as the count size, then your counter will increment by 2 and go through the table twice as fast, thus we see that the frequency resolution or step size is given as:
$$f_{step} = \frac{f_{clk}}{2^{acc}}$$
Where $f_{step}$ is the frequency step size of the output, $f_{clk}$ is your NCO update rate (master clock), and $acc$ is the accumulator size in bits.
The output frequency in Hz is given as:
$$f_{out} = F f_{step}= F \frac{f_{clk}}{2^{acc}} $$
Where $F$ is the frequency control word in counts. If you are using a 12 bit accumulator then the frequency control word should be 11 bits such that $F$ goes from $0$ to $2^{acc-1}$ in order to include the complete Nyquist range of unique frequencies. To generate a chirp you simply change the frequency control word on each cycle of the master clock in accordance with the instantaneous frequency desired over time. Be sure to also read this post on properly implementing a frequency ramp and the common "gotcha" with doing that.
The truncation or reduction of bits allows for very high frequency resolution by using a large accumulator size (typically 32 to 48 bits) while enabling realizable memory tables for the stored sinusoidal output, at the expense of output Signal-to-Noise Ratio (SNR) as limited by phase truncation spurs. There are further memory reduction techniques such as quarter cycle storage (you only need to have a quarter cycle of the sine wave and can count up/down and change sign appropriately to create a full size wave), memory reduction through interpolation techniques, and reduction algorithms such as the Hutchinson algorithm and Sunderland algorithm. Phase truncation spurs can be further reduced through dithering which is done by adding random noise patterns at the Phase Control Word input typically not more than a few bits in total rms level.
The relationship between SNR and phase truncation is given as:
$$SNR_{\phi}= 6.02 \text{ dB/bit} - 5.172 \text{ dB}$$
Where bit represents the look-up table address size, not the output data width, and $SNR_{\phi}$ is the total noise due to the power of the phase truncation spurs alone relative to the desired output tone.
Additionally the Spurious Free Dynamic Range (SFDR) which is the power of the strongest spur relative to the desired output tone is given as:
$$SFDR = 6.02 \text{ dB/bit}$$
Where again I am using bit here to refer to the look-up table address width.
The other noise source is the quantization noise of the output which is controlled by the data width selected for the output and is given by:
$$SNR_q= 6.02 \text{ dB/bit} + 1.76 \text{ dB}$$
Where here bit represents the output data width and $SNR_q$ is the total noise due to quantization alone relative to the desired output tone.
This is depicted below with the addition of a "Phase Control Word" for optional phase modulation in addition to the frequency modulation capability provided by the "Frequency Control Word". An additional multiplier after the look-up table output could be used to provide amplitude modulation capability for a universal phase/frequency/amplitude modulator.
The mathematical view is understood by realizing that an accumulator is the digital counterpart of an integrator and phase is the integration of frequency (freq is a change of phase over a change in time, or $df/dt$).
Two more hands on options. We use an example of a sweep that goes from 1kHz to 10 kHz in 2048 samples with a sample rate of 48 kHz.
Method one: phase accumulator and look up table
Simply use a phase accumulator as index into the look up table. After each sample, increase the phase by the current frequency and then increase the frequency by the sweep rate. This can be very easily done in fixed point. See Matlab code below.
% Parameters
fs = 48000; % sample rate in Hz
f1 = 1000; % start ferquency in Hz
f2 = 10000; % end frequency in Hz
sweepLength = 2048; % length of the sweep in samples
M = 1; % matlab array offset
% cosine table
tableSize = 4096;
cosTable = cos(2*pi*(0:tableSize-1)'/tableSize); % cosine table
% calculate some constants,
w1 = f1/fs*tableSize;
w2 = f2/fs*tableSize;
wStep = (w2-w1)./(sweepLength);
% initialize output
x6 = zeros(sweepLength,1);
% turn anything into 24-bit fixed. We use 1 overflow bit, 12 table index
% bits and 11 fractional bits. Real hardware wouldn't need an overflow bit
% as long as the addition wraps on overflow
% state variables in Q13.11
phi = uint32(0); % phase accumulator
w = uint32(w1*2^11); % current frequency
wStep = uint32(wStep*2^11); % frequency increment
for t = 0:sweepLength-1
% turn phase into lookup index including rounding
i0 = bitshift(phi+2^10,-11);
% do the table lookup
x6(t+M) = cosTable(M+i0);
% update the phase accumulator and wrap around if it overflows
phi = bitand(phi + w,2^23-1);
% increment the freqyency, this cannot overflow
w = w + wStep;
end
Method 2: phasor rotation, no lookup table required
Using complex multiplication with a phasor $e^{j\omega t}$ is a very efficient way to build oscillators without lookup tables or transcendent functions. It's basically a recursion:
$$Z[k] = Z[k-1] \cdot W[k], W[k] = W[k-1] \cdot W_d$$
This eliminates the need for a look up table. It can be easily done in fixed because all state variables are between -1 and +1 so simple fractional multiples will do.
%% Parameters
fs = 48000;
f1 = 1000;
f2 = 10000;
sweepLength = 2048;
% some constants
frequencyStep = (f2-f1)/(sweepLength-1);
om1 = 2*pi*f1/fs;% angular frequency
om2 = 2*pi*f2/fs;
omStep = 2*pi*frequencyStep/fs;
wd = exp(1i*omStep);
x3 = zeros(n,1); % initialze output
% state variables
z = 1;
w = exp(1i*om1);
% sample loop
for t = 1:n
x3(t) = real(z); % take the output, real for cosine sweep,
% imag for sine sweep
z = z*w; % phasor multiplication
w = w*wd; % frequency increment
end