{-----------------------------------------------------------------------------
decimate.asm: Real Time Decimator
------------------------------------------------------------------------------
Description:
Decimation is simply digital resampling with anti-aliasing. This
implementation uses an N-tap Direct Form Finite Impulse Response Filter, and
decimates in real time by M for a decrease of 1/M times the input sample rate.
The input signal to the decimator is a signal which has been oversampled by M,
i.e., has been sampled at M times the desired output frequency. This is often
done to allow the use of a lower-order input filter than would be necessary if
the signal were sampled at the desired frequency. The decimator uses an FIR
filter to low-pass filter the incoming signal, and removes M-1 out of every M
samples to create an output signal which is 1/M times the input signal.
Before decimation, the input signal must be bandlimitted to one-half of the
output frequency.
In this example, the timer is first programmed to the desired input frequency.
The timer service routine (tmzh_svc) gets the input sample from an A/D
converter port (adc). An input buffer is used to store samples. This allows
the filter computations to proceed in parallel with the acquisition of the
next M inputs, allowing a larger order filter to be used than if all
calculations had to be made between samples. When M samples have been
acquired, the input buffer is full and a Circular Buffer overflow interupt is
generated. The Circular Buffer overflow service routine (cb7_svc) calls the
decimator (decimate), which transfers the input buffer to a data buffer, and
runs the FIR filter on the sampled data. The output of the FIR is written to
the output D/A converter (dac).
------------------------------------------------------------------------------
Program Characteristics:
Input: adc
Output: dac
Labels:
init_dec reset-time initialization
decimate called by CB7 interrupt
Data Registers Used:
r0 FIR data in / ADC fltg-pt data
r4 FIR coefficients / temporary reg
r8 FIR accumulate result reg
r12 FIR multiply result reg / temporary reg
r14 = 16 = exponent scaling value
r15 ADC input data
Computation Time:
cb7_svc = 6 + decimate = N + M*2 + 11
decimate = N + M*2 + 5 + [5 misses]
tmzh_svc = 5
Minimum Timer Period: max( ceil[(N+2*M+11+5*M)/M], 20 )
This calculation certainly deserves a footnote! The decimator must basically
calculate one pass of the FIR filter while M samples are being read into an
input buffer. The FIR filter is invoked by cb7_svc, which requires (N+M*2+11)
cycles to execute. M samples require an additional (5*M) cycles to acquire.
Dividing by M yields the timer period, but we must take the greatest integer
value larger than this number (ceiling, or ceil function). Additionally, the
input buffer must be completely moved to the data buffer area buffer at the
start of the cb7_svc routine before the first timer interrupt comes in. There
will be insufficient time for this if the timer period is not at least 20.
Here is an example calculation:
N=number of taps=64
M=decimation factor=8
Mimimum Timer Period=max(ceil[(64+2*8+11+5*8)/8],20)
=max(ceil[( 131 /8)],20)
=max(ceil[( 131 /8)],20)
=max(17, 20)
=20
Maximum Input Frequency= FP/20 = 20MHz/20 = 1.0 MHz
Maximum Output Frequency= FP/(M*20) = 20MHz/(8*20) = 125 KHz
if N=128, 25 taps would be the minimum.
------------------------------------------------------------------------------
Creating & Using the Test Case:
Running the test case requires two data files:
coef.dat 32-tap FIR filter coefficients (floating point)
wave.1 waveform data file
coef.dat contains a 32-tap FIR filter which bandlimits the input signal to 1/8
of the input frequency. Since the decimator in the test case decimates by a
factor M=4, this bandlimitation is equivalent to the required limit of 1/2 the
output frequency. The filter is a Parks-McClellan filter with a passband
ripple of 1.5dB out to about 1/10 the input frequency, and a stopband with
greater than 70dB of attenuation above 1/8 the input frequency.
As an example, if the oversampled input frequency is 64kHz, the passband
extends out to about 6.4kHz. The stopband starts at 8kHz, and the output
frequency is 16kHz.
The example data file is wave.1. It contains 512 signed fixed-point data
which emulate a 16-bit A/D converter whose bits are read from Data Memory bits
39:24. wave.1 contains a composite waveform generated from 3 sine waves of
differing frequency and magnitude. The cb7_svc routine arithmetically shifts
this data to the right by 16 bits, effectively zero-ing out the garbage data
which would be found in bits 23:0 if a real 16-bit ADC port were read.
The test case writes the decimated output to a dac port. Since there are 512
samples in wave.1, and the test case performs decimation by 4, there will be
128 data values written to the dac port if all data samples are read in and
processed. The values written out are left-shifted by 16 bits in parallel
with the float-->fixed conversion, based again on the assumption that the D/A
converter port is located in the upper 16 bits of data memory.
Armed with this information, you are ready to run:
1. Assemble & Start Simulator:
asm21k -DTEST decimate
ld21k -a generic decimate
sim21k -a generic -e decimate -k port1
Note: the keystroke file port1.ksf opens two ports:
1st port - DM, F0000000, Fixed-Pt., auto-wrap, wave.1
2nd port - DM, F0000001, Fixed-Pt., no wrap, out.1
2. In the simulator,
a. Go to the program memory (disassembled) window,
d. Go to symbol cb7_done, set to break on 129th occurence, and
c. Run
3. Compare wave.1 to out.1 on the graphing program of your choice.
------------------------------------------------------------------------------
Author: Jim Donahue, Analog Devices DSP Division
Created: 3/4/91
-----------------------------------------------------------------------------}
#include "def21020.h"
#define N 32 /* number of taps */
#define M 4 /* decimation factor */
#define FP 20.0e6 /* Processor Frequency = 20MHz */
#define FI 64.0e3 /* Interrupt Frequency = 64KHz */
#ifndef TEST /*----------------------------------------------------------*/
#define TPER_VAL 312 /* TPER_VAL = FP/FI - 1 */
#else /*-----------------------------------------------------------------*/
#define TPER_VAL 19 /* interrupt every 20 cycles */
#endif /*----------------------------------------------------------------*/
.SEGMENT /dm dm_data;
.VAR data[N]; /* FIR input data */
.VAR input_buf[M]; /* raw A/D Converter data */
.ENDSEG;
.SEGMENT /pm pm_data;
.VAR coef[N]="coef.dat"; /* FIR floating-point coefficients */
.ENDSEG;
.SEGMENT /dm ports;
.PORT adc; /* Analog to Digital (input) converter */
.PORT dac; /* Digital to Analog (output) converter */
.ENDSEG;
{-------------------------------------------------------------------------------
RESET Service Routine
-------------------------------------------------------------------------------}
.SEGMENT /pm rst_svc;
rst_svc: PMWAIT=0x0021; { no wait states,internal ack only }
DMWAIT=0x8421; { no wait states,internal ack only }
jump init_dec; { initialize the test case }
.ENDSEG;
{-------------------------------------------------------------------------------
TMZH Service Routine
-------------------------------------------------------------------------------}
.SEGMENT /pm tmzh_svc;
{ sample input: this code occurs at the sample rate }
tmzh_svc: rti(db);
r15=dm(adc); { get input sample }
dm(i7,m0)=r15; { load in M long buffer }
.ENDSEG;
{-------------------------------------------------------------------------------
DAG1 Circular Buffer 7 Overflow Service Routine
-------------------------------------------------------------------------------}
.SEGMENT /pm cb7_svc;
{ process input data when circular buffer 7 wraps around ("overflows") }
{ this code occurs at 1/M times the sample rate }
cb7_svc: call decimate (db); { call the decimator }
r12=dm(i7,m0);
r4=ashift r12 by -16;
rti (db); { return from interrupt }
r0 = fix f0 by r14; { convert to fixed point }
cb7_done: dm(dac)=r0; { output data sample }
.ENDSEG;
{-------------------------------------------------------------------------------
efficient decimator initialization
-------------------------------------------------------------------------------}
.SEGMENT /pm pm_code;
init_dec: b0=data; l0=@data; m0=1; { data buffer }
b7=input_buf; l7=@input_buf; { input buffer }
b8=coef; l8=@coef; m8=1; { coefficient buffer }
tperiod=TPER_VAL; { program timer }
tcount=TPER_VAL;
{ enable Timer high priority, CB7 interrupts }
bit set imask TMZHI|CB7I;
{ clear interrupts before setting global enable }
bit clr irptl TMZHI|CB7I;
r14=16; { scale factor float-->fixed}
r15=0; { clear data buffer }
lcntr=N, do clrbuf until lce;
clrbuf: dm(i0,m0)=r15;
{ enable interrupts, nesting mode, ALU saturation }
bit set mode1 IRPTEN|NESTM|ALUSAT;
bit set mode2 TIMEN; { enable the timer }
wait: idle;
jump wait; { infinite wait loop }
.ENDSEG;
{-------------------------------------------------------------------------------
efficient decimator routine
executes at 1/M times the sample rate
-------------------------------------------------------------------------------}
.SEGMENT /pm pm_code;
decimate: { Transfer input samples to the FIR data memory space }
{ using the input buffer working pointer. Perform }
{ fix-->float conversion and right-shifting in parallel. }
{ Note: this loop MUST be unrolled if M<3. It would be }
{ more efficient if unrolled for any M<4. The 1st two }
{ instructions of this loop have been executed in the delay }
{ field of the call to this subroutine }
f0=float r4, r12=dm(i7,m0);
lcntr=M-2, do load_data until lce;
r4=ashift r12 by -16, dm(i0,m0)=f0;
load_data: f0=float r4, r12=dm(i7,m0);
r4=ashift r12 by -16, dm(i0,m0)=f0;
f0=float r4;
dm(i0,m0)=f0;
{ N-tap FIR filter }
fir: f0=dm(i0,m0), f4=pm(i8,m8);
f8=f0*f4, f0=dm(i0,m0), f4=pm(i8,m8);
f12=f0*f4, f0=dm(i0,m0), f4=pm(i8,m8);
lcntr=N-3, do taps until lce;
taps: f12=f0*f4, f8=f8+f12, f0=dm(i0,m0), f4=pm(i8,m8);
rts(db), f12=f0*f4, f8=f8+f12;
f0=f8+f12; { final sum in || w/ rts }
bit clr stky CB7S; { clear sticky bit to }
{ prevent re-interrupt }
.ENDSEG;