{----------------------------------------------------------------------------- rat_2int.asm: Interrupt-Driven Real Time Rational Rate Changer ------------------------------------------------------------------------------ Description: Uses an N-tap Real time Direct Form Finite Impulse Filter. Program performs interpolation by L and decimation by M for a L/M change in the input sample rate. Uses an input buffer so that the filter computations can occur in parallel with the input and output of data. This allows the use of a larger order filter for a given input sample rate. Two external timers are necessary; they activate interrupt IRQ3 at the input sample rate, IRQ2 at the output sample rate. ------------------------------------------------------------------------------ Theory: See Theory section of decimate.asm and interpol.asm. ------------------------------------------------------------------------------ Program Characteristics: Input: adc. Output: dac. Start Labels: Altered Registers: r0 FIR data in / ADC fltg-pt data / temp register r1 temp register r2 input counter r3 output counter r4 FIR coefficients r5 scale value L r8 FIR accumulate result reg r12 FIR multiply result reg / temporary reg r14 = 16 = exponent scale factor r15 ADC data raw Computation Time: irq2_svc = 6 irq3_svc = 3 + output output >= NoverL + 14 ( Notes: 1,3 ) <= NoverL + 2*intMoverL + 13 ( Notes: 2,3 ) Notes: 1. w/ no input samples to transfer 2. w/max input samples to transfer 3. maximum of 5 cache misses on the 1st pass ------------------------------------------------------------------------------ Comments on Cryptic Source Code Ordering: There are several instances of unusual instruction ordering in the "output" subroutine. This ordering is used in order to minimize total execution time, but requires explanation. The principal difficulty is that modifying an I or M register followed by use of the same DAG in the next instruction causes a single NOP cycle to be inserted. As explained in the 21020 User Guide, this dead cycle is inserted because the DAG needs to calculate indirect addresses for a given cycle during the previous cycle. To avoid this dead cycle, modifications of an I or M register are separated from use of a DAG where possible, even though the code ordering becomes more difficult to follow. For example, the index register i9 is used to track coefficient updates, and must be modified in the "output" subroutine. The instructions that must be run to do this are: modify(i9,-M); m10=i9; modify(i8,m10); i9=0; However, a dead cycle would occur after the 2nd instruction, and possibly after the 4th instruction if the instruction after it used DAG2. To avoid this, the first two instructions have been moved away from the 3rd, and the 4th instruction has been moved to a place where a DAG2 operation does not follow it. Additionally, there are two places in the "output" subroutine where "i7=b7" must be executed. To do this ureg<-->ureg transfer by itself is wasteful, so it has been combined with unrelated computations to form a multifunction instruction. The first i7=b7 must occur before the loop in which the input buffer is dumped into the delay line. This has been moved earlier by several cycles in order to combine it with an unrelated computation, saving one cycle. The second i7=b7 can occur immediately after the input buffer has been transferred, but it is delayed until it can be combined with an unrelated computation. ------------------------------------------------------------------------------ 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 Descriptions of coef.dat and wave.1 can be found in decimate.asm & interpol.asm. The test case writes the rate-changed output to a dac port. Since there are 512 samples in wave.1, and the test case performs interpolation by 4 followed by decimation by 3, there will be 682 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 rat_2int ld21k -a generic rat_2int sim21k -a generic -e rat_2int -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. Run keystroke file setint.ksf, which programs periodic interrupts, a. Go to the program memory (disassembled) window, d. Go to symbol irq3_svc, set to break on 513th 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 /* N taps, N coefficients */ #define L 4 /* interpolate by factor of L */ #define NoverL 8 /* N must be an integer multiple of L */ #define M 3 /* decimate by factor of M */ #define intMoverL 2 /* smallest integer GE M/L */ .SEGMENT /pm pm_data; .VAR coef[N]="coef.dat"; { coefficient circular buffer } .ENDSEG; .SEGMENT /dm dm_data; .VAR data[NoverL]; { data delay line } .VAR input_buf[intMoverL]; { input buffer is not circular } .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_rat; { initialize the test case } .ENDSEG; {------------------------------------------------------------------------------- IRQ3 Interrupt Service Routine occurs at input rate -------------------------------------------------------------------------------} .SEGMENT /pm irq3_svc; irq3_svc: r0=dm(adc); { get input sample } r0=ashift r0 by -16; { right-justify the data } { convert fixed->float, modify the coef update by L } f0=float r0,modify(i9,m8); rti(db); f0=f0*f5; { scale by L } dm(i7,m0)=f0; { load in M long buffer } .ENDSEG; {------------------------------------------------------------------------------- IRQ2 Interrupt Service Routine occurs at output rate -------------------------------------------------------------------------------} .SEGMENT /pm irq2_svc; irq2_svc: jump output (db); { go to the output routine } r0=i7; { get input buffer pointer } r1=input_buf; { get start addr of input buffer } .ENDSEG; {------------------------------------------------------------------------------- Initialize Interpolation Routine -------------------------------------------------------------------------------} .SEGMENT /pm pm_code; init_rat: b0=data; l0=@data; m0=1; { data buffer } b7=input_buf; l7=0; { input buffer } b8=coef; l8=@coef; m8=L; { modifier for coef is L! } b9=0;l9=0;m9=-M; { track coefficient updates } r5=L; { scale reg for interpolator } f5=float r5; { fix --> float conversion } r14=16; { exponent scale factor } r0=0; { clear data buffer } lcntr=NoverL, do clrbuf until lce; clrbuf: dm(i0,m0)=r0; { enable timer zero high priority, software 0 interrupts } bit set imask IRQ3I|IRQ2I; bit clr irptl IRQ3I|IRQ2I; { clear any pending irpts } bit set mode1 IRPTEN|ALUSAT; { enable interrupts, ALU sat} wait: idle; { infinite wait loop } jump wait; .ENDSEG; {------------------------------------------------------------------------------- Calculate output initiated by IRQ2, occurs at output sample rate -------------------------------------------------------------------------------} .SEGMENT /pm pm_code; output: r1=r0-r1,i7=b7; { r1 = # samples in input buffer } { also reset input buffer } if eq jump mod_coef(db);{ skip do loop if buffer empty } modify(i9,-M); { modify coef update by -M } m10=i9; { dump the buffer into delay line } lcntr=r1, do load_data until lce; f1=dm(i7,m0); load_data: dm(i0,m0)=f1; mod_coef: modify(i8,m10); { modify coef ptr by coef update } { filter pass, occurs at L times the input sample rate } 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=NoverL-3, do taps until lce; taps: f12=f0*f4, f8=f8+f12, f0=dm(i0,m0), f4=pm(i8,m8); f12=f0*f4, f8=f8+f12; f0=f8+f12, i7=b7; { reset input buffer in || w/comp } i9=0; { reset coef update } rti (db); r0 = fix f0 by r14; { float -> fixed } dm(dac)=r0; { output data sample } .ENDSEG;