Professional Documents
Culture Documents
ADSP-2181 Experiments
Sophocles J. Orfanidis
ADSP-2181 DSP
sample
processing
algorithm
x
y
AD1847
analog input
rx-buf
codec
tx-buf
analog output
fs
Contents
1 Introduction
2 Getting Started
2.1 Running DSP Programs . . . . . .
2.2 Decimal to Hex Format Converters
2.3 Wavetable Generator Programs . .
2.4 Subdirectory Structure . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
1
3
4
5
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5
6
7
9
10
10
12
12
15
16
4 Experiments
4.1 Sampling and Quantization . . .
4.2 Delays . . . . . . . . . . . . . . .
4.3 FIR Filters . . . . . . . . . . . . .
4.4 Comb Filters . . . . . . . . . . .
4.5 Plain Reverb . . . . . . . . . . . .
4.6 Allpass Reverb . . . . . . . . . .
4.7 Lowpass Reverb . . . . . . . . . .
4.8 Schroeders Reverb . . . . . . . .
4.9 Stereo Reverb . . . . . . . . . . .
4.10 Reverberating Delay . . . . . . .
4.11 Multi-Delay Effects . . . . . . . .
4.12 Multitap Delay Effects . . . . . .
4.13 Karplus-Strong String Algorithm
4.14 Wavetable Generators . . . . . .
4.15 Notch Filters . . . . . . . . . . .
4.16 Flangers and Phasers . . . . . . .
4.17 Simulator Examples . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
16
16
23
25
32
37
41
46
48
52
55
57
59
62
64
69
80
85
5 Macros
5.1 zero .
5.2 tap . .
5.3 tapin .
5.4 cdelay
5.5 dot . .
5.6 cfir . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
97
98
98
99
99
100
101
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
CONTENTS
115
INTRODUCTION
1. Introduction
This manual describes a number of hardware experiments illustrating the concrete
implementation of various DSP algorithms on the ADSP-2181 chip.
The experiments include quantization and aliasing effects; the circular buffer
implementation of delays, FIR, and IIR filters; the canceling of periodic interference
with notch filters; wavetable generators; and several audio effects, such as comb
filters, flangers and phasers, plain, allpass, and lowpass reverberators, Schroeders
reverberator, and several multi-tap, multi-delay, and stereo-delay type effects, as
well as the Karplus-Strong string algorithm. All of the experiments are based on
the text [1].
Our aim in these experiments is not to necessarily write the most efficient assembly code, but rather to show beginning DSP students how straightforward and
fun it is to program a DSP chip and hear the algorithms in action. Thus, we have at
times sacrificed efficiency for clarity.
To facilitate the programming of these applications, we have written a number of
assembly code macros that closely parallel some of the C routines in the text, such
as cdelay and tap, and allow the manipulation of circular delay-line buffers and
the building up of more complex block diagrams. Our use of circular buffers and
pointers is identical to that in the text (e.g., filter coefficients and states are stored
in forward order and a delay is implemented by decrementing the buffer pointer.) A
number of DOS utilities are also included, such as decimal-to-hex format converters
and wavetable generators.
All of the included programs are based on the talkthru example program given
in the EZ-KIT Lite Reference Manual [2]. All processor and codec initialization details have been hidden away in two include-files, begin.dsp and end.dsp, simplifying the structure of the programs and allowing the students to concentrate on
the translation of their sample processing algorithm to assembly code.
Several reference resources are available. Besides the EZ-KIT Reference Manual
and Data Sheets included in the kits, the following reference books may be obtained
(in PDF format) from the Analog Devices web page www.analog.com:
1. ADSP-2100 Family Users Manual (also included in the newest kits.)
2. DSP Applications Using the ADSP-2100 Family, vols. 1 & 2.
2. Getting Started
2.1. Running DSP Programs
The process of running a signal processing algorithm on the ADSP-2181 chip of the
EZ-KIT Lite board consists of three stages:
1. Creating a text source file containing the assembly code implementation of
the algorithm. The default filename extension is .dsp. The DOS edit or emacs
text editors may be used to edit the source file. They can be invoked by the
DOS commands:
GETTING STARTED
edit filename.dsp
emacs filename.dsp
2. Compiling and linking the source file using the assembler and linker programs
asm21.exe and ld21.exe. These operations have been automated into the
DOS batch file ezk.bat, which can be invoked as follows:
ezk filename
This loads filename.exe onto the processor and begins execution immediately. One can also load the program and enter a menu-oriented terminal
program by the command:
ezl filename t
The above utilities are in the path and can be executed from any subdirectory. The
doskey synonym dsp changes to the directory c:\adi_dsp\examples below which
are the subdirectories of the various examples.
There are a number of other useful utilities available. The batch file ezs.bat
will compile and link a source code file and then run the simulator sim2181.exe:
ezs filename
where the full name filename.dsp is assumed. Some simulator example programs
are in the examples subdirectory sim. The examples include quantization and
downsampling, an order-3 delay, an FIR, and an IIR filter implemented using circular buffers. In the simulator, one can step through every instruction in the program
and observe the contents of the delay-line buffer registers as they change from one
input sample to the next. There is also the DOS batch file mkezk.bat with usage:
mkezk filename
GETTING STARTED
For example, the following column of decimal numbers fed into dec2hex 1.15 format produces the second column of hex numbers. In turn, the hex numbers fed into
hex2dec 1.15 format will produce the third column of decimal numbers, which are
the original numbers rounded to 16-bit accuracy:
1.00000
0.50000
0.40000
0.00003
-0.00003
-0.40000
-0.50000
-1.00000
0x7fff
0x4000
0x3333
0x0001
0xffff
0xcccd
0xc000
0x8000
0.999969482421875
0.500000000000000
0.399993896484375
0.000030517578125
-0.000030517578125
-0.399993896484375
-0.500000000000000
-1.000000000000000
The overall range of representable numbers depends on the format. For example,
the above column of hex numbers fed into the hex2dec 2.14 format will produce
the third column scaled up by a factor of two. To determine which a.b format to
use, recall that all numbers to be converted must lie within the formats range:
2a1 x 2a1 2b
Thus, for example, the ranges for the 1.15, 2.14, and 3.13 formats will be:
1 x 1 215
2 x 2 214
4 x 4 213
GETTING STARTED
x = (b1 2 + b2 2 + b3 2 + + bB 2 )
x = (b1 21 + b2 22 + b3 23 + + bB 2B )2a
(0.B format)
(a.b format)
x = (b1 2
+ b2 2
+ b3 2
+ + bB )
x = (b1 2B1 + b2 2B2 + b3 2B3 + + bB )2b
B1
B2
B3
(B.0 format)
(a.b format)
The first generates one period of length 2000 samples of a unit-amplitude sinusoid,
and the second a period of a cosinusoid, that is,
x1 (n)= sin
-2
0.16
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
,
x2 (n)= cos
2n
,
n = 0, 1, . . . , D 1
with D = 2000. The third and fourth examples generate one period of the square
waves:
x3 (n) = [1, 1, . . . , 1, 1, 1, . . . , 1]
1000 ones
-15
1000 minus-ones
x4 (n) = [0, 1, 1, . . . , 1, 0, 1, 1, . . . , 1]
-16
1.15
-2
2n
where b = 0, 1, . . . , B. Fig. 2.1 shows the bit weighting factors and the placement
of the fractional point for the four formats 0.16, 1.15, 2.14, and 3.13.
-1
999 ones
999 minus-ones
The square wave x3 (n) jumps discontinuously from level 1 to 1, whereas x4 (n)
has the value 0 at the discontinuities. See Section 4.15 for more on this.
-2
2.14
-1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-2
3.13
-1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
The programs dec2hex and hex2dec can be applied to any B which is a multiple
of 4. For example, they can convert to/from the 1.7, 1.23, or 1.31 formats (which
are 8-bit, 24-bit, and 32-bit formats) and their variants, such as the 2.6, 3.21, or 2.30
formats.
The default installation directory of the EZ-KIT Lite software is c:\adi_dsp. The
various batch files, DOS utilities, and DSP macros for this lab are in the subdirectory
macros. The various examples are in the subdirectory examples. The detailed
contents of these directories are listed below:
c:\adi_dsp\macros:
ezk.bat
dec2hex.exe
uran.exe
trapztbl.c
cdir.dsp
tap.dsp
ezl.bat
hex2dec.exe
dec2hex.c
uran.c
cfir.dsp
tapin.dsp
ezs.bat
sinetbl.exe
hex2dec.c
begin.dsp
dot.dsp
wavgen.dsp
mkezk.bat
squartbl.exe
sinetbl.c
ccan.dsp
dspmac.dsp
zero.dsp
trapztbl.exe
squartbl.c
cdelay.dsp
end.dsp
template.dsp
c:\adi_dsp\examples:
allpass
lowpass
revdel
comb
multidel
reverb
delay
multitap
sim
fir
notch
stereo
flanger
plain
wavtable
guitar
quantize
However, to get started writing simple filtering signal processing algorithms, one
needs only a small subset of the full set.
Temporary variables, such as delay-line buffers and multiplier coefficients, can
be stored in data memory (DM) or program memory (PM) from where they can be
transferred back and forth to the processors computational units where all the
arithmetic operations are carried out.
ar = x + y;
ar = x - y;
ar = y - x;
The shifter instructions that we will be using are the arithmetic shifts:
The three computational units are the multiplier accumulator (MAC), the arithmetic
logic unit (ALU), and the shifter. The MAC, ALU, and shifter registers that we will
be using in our examples are the following:
(MAC)
(ALU)
(shifter)
bits:
bits:
bits:
bits:
mx0, mx1, my0, my1, mr0, mr1, ax0, ax1, ay0, ay1, ar, sr0, sr1, si
mr (consists of mr2, mr1, mr0)
sr (consists of sr1, sr0)
mr2, se
The 40-bit accumulator register mr of the MAC consists of the three registers mr0,
mr1, mr2. The result of multiplying two 16-bit numbers is a 32-bit number placed
in the registers mr1, mr0, where mr1 contains the 16 MSB bits and mr0 the 16 LSB
bits. The 8-bit register mr2 is used for overflow bits. The subset of MAC operations
that we will be using is:
mr
mr
mr
mr
mr
mr
mr
if
= 0;
= x * y (ss);
= x * y (rnd);
= mr + x * y (ss);
= mr + x * y (rnd);
= mr - x * y (ss);
= mr - x * y (rnd);
mv sat mr;
The effect of this instruction is to scale x by the factor 2exp and place the result
in sr, with sr1 containing the 16 MSB bits. In the second ashift instruction, the
value of the exponent exp has been preloaded into the 8-bit exponent register se
and is read from there.
.const a = 0x6000;
.const D = 3;
.var/dm w[D+1];
.var/dm x, y;
Then, the following examples of data transfers are all executable in one cycle each:
(saturate if overflow occurs)
Note that x must always be written to the left of y. The (ss) qualifier treats the
operands as signed 1.15 numbers and the MAC operation is carried out to full
double-precision. The qualifier (rnd) causes the double-precision 32-bit result to
be rounded off to its 16 most significant bits, with the result residing in mr1. The
last, if mv sat mr, instruction saturates mr to its largest (positive or negative)
value whenever the overflow flag mv is raised. The subset of ALU operations that
we will be using is:
mr1 = 0;
my1 = a;
ax1 = 0x4000;
ar = sr1;
mx1 = ay0;
mr1 = dm(x);
dm(y) = my1;
mr1 = dm(w);
mr1 = dm(w+1);
dm(w+2) = mr1;
{load
{load
{load
{load
{load
{load
{load
{load
{load
{load
Introducing the internal state w1 (n)= y(n 1), and noting that the next state is
w1 (n + 1)= y(n), we may draw a block diagram realization and write the sample
processing algorithm as follows:
z-1
w1
y = aw1 + bx
w1 = y
To implement this example on the EZ-KIT Lite board, we must be able to read
input samples from the codec and write the output samples back to the codec.
In the sample talkthru program from the EZ-KIT Lite Manual [2], this is accomplished by using the so-called autobuffering technique of the ADSP-2181 serial
ports, which allows the samples from the stereo codecs analog input to be written
to DM memory at a predefined receive-buffer location, called rx_buf. Samples from
the left and right channels are stored in rx_buf+1 and rx_buf+2.
After both stereo samples have been written to DM, a receive-interrupt is issued
to the DSP, which then initiates an interrupt service routine that implements the DSP
sample processing algorithm to be applied to the input samples. The sampling rate
fs (selected through the codecs format register) determines the rate at which these
interrupts are being issued to the DSP, and therefore, the rate at which the sample
processing algorithm is repeatedly executed, generating the output samples.
The processed left/right output samples are then written back to some predefined transmit-buffer location in DM, called tx_buf, from where they are passed on
to the DSPs serial port and on to the codec to be converted to analog output. The
overall sequence of operations is shown in Fig. 3.1.
mx0 = b;
mr = mx0 * my0 (ss);
mx0 = a;
my0 = dm(w1);
mr = mr + mx0 * my0 (rnd);
dm(w1) = mr1;
b into my0}
dm(tx_buf + 2) = mr1;
a}
DM}
= output sample}
Note that after the final MAC, only the rounded 16-bit MSB part of mr, contained in
mr1, is sent to the codec and saved into the internal state memory location.
ADSP-2181 DSP
sample
processing
algorithm
x
y
AD1847
rx-buf
analog input
codec
tx-buf
analog output
fs
Fig. 3.1 Sample by sample processing on the EZ-KIT Lite board.
For our simple example, we start with the variable declarations and initializations:
.const a = 0x6000;
.const b = 0x2000;
.var/dm w1;
ax0 = 0;
dm(w1) = ax0;
{a = 0.75}
{b = 0.25}
{filters internal state}
{initialize w1 to zero}
Then, the sample processing algorithm (for the right channel only) will be:
my0 = dm(rx_buf + 2);
writes into mr1 the contents of the DM memory location pointed to by i2 and then
changes i2 by an amount m2. If m2=1, then the new i2 will point to the next memory location, and if m2=-1, it will point to the previous location. This post-modify
scheme applies also to writing into memory. For example, the instruction
dm(i2, m2) = mr1;
writes the contents of mr1 into the memory location pointed to by i2 and then
changes i2 by an amount m2. A useful related instruction is the modify instruction,
which allows i2 to be changed by a desired amount m2 without reading or writing
data:
modify(i2, m2);
The DAG registers can keep track of the memory locations for both linear and circular buffer arrays. Thus, they are especially convenient for implementing circular
delay-line buffers.
Fig. 3.2 shows how the DAG pointer i2 points into a circular buffer, just like the
usual pointer p of the text [1]. The value of m2 specifies by how much i2 is to move.
As i2 moves up or down within the buffer locations, it wraps around automatically
if it exceeds the upper or lower bounds.
10
DM
...
...
p = w+q
circular
buffer
where d is in the range d = 0, 1, . . . , D, and mr1 can be replaced by any other 16-bit
computational register. Fig. 3.3 illustrates these operations. One caveat on the use
of tap is that it cannot be used as described when the delay d is variable and is
passed through a register (because we cannot set m2 = -dreg). See Section 4.16
for further discussion of this point and a remedy.
wD
DM
...
...
wq
.const D = 100;
.var/dm/circ w[D+1];
{placed in DM memory}
i2 = ^w;
L2 = %w;
The do-loop iterates L2 times and therefore, because the buffer is circular, the
pointer i2 will wrap around completely and, upon exit, it will be pointing again at
the beginning of the buffer w. This initialization loop has been made into a macro,
zero.dsp, and is invoked by
...
i2+d
sd
...
To implement a delay of some maximum amount, say D = 100 samples, one must
declare it as a circular (D+1)-dimensional array, then set one of the I-registers
to point to the beginning of the buffer, and define the corresponding L-register to
contain the length of the buffer:
DAG1
i0 m0 L0
i1 m1 L1
i2 m2 L2
i3 m3 L3
s0
circular
buffer
d
dth tap
wD
In filtering operations, we must put a new input sample into tap-0 before updating the delay line. Such operation can be accomplished by
m2 = 0; dm(i2, m2) = mx1;
which puts the value of mx1 into the location pointed to by i2 without post-modifying
i2. These steps have been automated into another macro, tapin.dsp, with usage:
tapin(i2, m2, mx1);
where the mx1 register can be replaced by any other 16-bit register. Fig. 3.4 illustrates this operation.
DM
w0
w1
...
These steps have been put into a macro, tap.dsp, with usage:
i2
s0
0th tap
dreg
...
Once the circular buffer w and pointer i2 have been defined, the delay-lines tap
outputs can be obtained by accessing the buffer entries relative to i2. For example,
the value contained in the d-th tap, can be obtained by the instructions:
DAG1
i0 m0 L0
i1 m1 L1
i2 m2 L2
i3 m3 L3
wD
...
m2 = d; modify(i2, m2);
m2 =-d; mr1 = dm(i2, m2);
dreg
...
i2
...
w0
m2 = 1;
cntr = L2;
do loop until ce;
loop: dm(i2, m2) = 0;
11
...
i2
w0
w1
DAG1
i0 m0 L0
i1 m1 L1
i2 m2 L2
i3 m3 L3
...
circular
buffer
12
13
s0
s1
z-1
Fig. 3.5 illustrates this operation. These two instructions have been placed into the
macro cdelay.dsp with usage:
s2
s3
cdelay(i2, m2);
DM
DM
...
...
w0
w0
wD
wD
before updating
after updating
p = s0 = x
s1 = tap(3, w, p, 1)
s2 = tap(3, w, p, 2)
s3 = tap(3, w, p, 3)
y = 2s0 3s1 2s2 + s3
cdelay(3, w, & p)
-3
-2
Since the range of the filter coefficients is [4, 4], we must convert them with
the 3.13 format, resulting in the hex numbers:
Equivalently, these are the filter coefficients scaled down by 4 in order to fit
within the 1.15 format, namely
1
[2, 3, 2, 1]= [0.50, 0.75, 0.50, 0.25] [0x4000, 0xa000, 0xc000, 0x2000]
4
...
...
s0
s1
...
i2
new
i2
...
DAG1
i0 m0 L0
i1 m1 L1
i2 m2 L2
i3 m3 L3
s-1 = sD
s0
...
i2 -1
z-1
...
z-1
circular
buffer
Because the MAC performs its multiplications in the 1.15 format, after the final
output y is computed, it can (optionally) be scaled up by a factor of 4 with the help
of the shifter. The following instructions declare the delay and filter coefficient
circular buffers and initialize them:
.const M = 3;
.var/dm/circ w[M+1];
.var/pm/circ h[M+1];
L2 = %w;
L4 = %h;
{filter order}
{delay-line buffer placed in DM}
{filter coefficient buffer placed in PM}
{coefficient values}
Assuming as in the previous example that the codec inputs and outputs come from
receive and transmit buffers in DM, the sample processing algorithm can be translated to assembly language as follows:
mx1 = dm(rx_buf + 2);
m2
mr
mr
mr
mr
mr
if
= 1; m4 = 1;
= 0, mx0 = dm(i2,m2), my0
= mr + mx0 * my0 (ss), mx0
= mr + mx0 * my0 (ss), mx0
= mr + mx0 * my0 (ss), mx0
= mr + mx0 * my0 (rnd);
mv sat mr;
=
=
=
=
pm(i4,m4);
dm(i2,m2), my0 = pm(i4,m4);
dm(i2,m2), my0 = pm(i4,m4);
dm(i2,m2), my0 = pm(i4,m4);
{s0,h0, next
{s1,h1, next
{s2,h2, next
{s3,h3, next
{mr = y}
s1,h1}
s2,h2}
s3,h3}
s0,h0}
14
15
The four code lines involving a MAC operation and fetching data from DM and
PM memories, are examples of multifunction instructions which are executable in
one cycle, that is, 30 nsec. These instructions are separated by commas instead of
semicolons.
The first multifunction instruction clears the MAC accumulator mr to zero, fetches
the values of s0 , h0 into the registers mx0, my0, and then post-increments the buffer
pointers to point to the next buffer entries, that is, s1 , h1 . (All of that is done in one
cycle.)
The next code line calculates the partial product mr = h0 s0 , fetches s1 , h1 , and
points to s2 , h2 . The next line updates the partial sum mr = h0 s0 + h1 s1 , fetches
s2 , h2 , and points to s3 , h3 . The next line updates the partial sum mr = h0 s0 +
h1 s1 + h2 s2 , fetches s3 , h3 , and wraps around to point to the beginning of the
circular buffers, namely, to s0 , h0 . The next line performs the final accumulation
mr = h0 s0 + h1 s1 + h2 s2 + h3 s3 , and rounds the result to its 16 most significant
bits, contained now in mr1. The result is saturated if overflow is detected.
Then, the delay line is updated by backshifting its pointer i2. Note that the
argument m2 of cdelay(i2,m2) is set internally to m2=-1 in the macro.
Then, the computed output in mr1 is scaled up by a factor of 4 by the shifter
and the scaled result is placed in sr1, and finally sent out to the codec.
The repeated multifunction instructions can be replaced by a do-loop, which effectively performs the dot-product of the internal states with the filter coefficients:
m2 = 1; m4 = 1;
mr = 0, mx0 = dm(i2,m2), my0 = pm(i4,m4);
{M = filter order}
cntr = M;
do dotloop until ce;
dotloop:
mr = mr + mx0 * my0 (ss), mx0 = dm(i2,m2), my0 = pm(i4,m4);
mr = mr + mx0 * my0 (rnd);
if mv sat mr;
These instructions have been collected into a macro, dot.dsp, with usage:
dot(M, i4, m4, i2, m2);
Fig. 3.6 illustrates the dot product operation. Note that i4, pointing to the filter
coefficients, cycles back to the beginning of the buffer, and i2, pointing to the filter
states, cycles back to the 0th state.
In summary, the sample processing algorithm can be simplified to the three
operations of: (a) reading the input sample into the delay line, (b) computing the
dot product output, and (c) updating the delay line:
mx1 = dm(rx_buf + 2);
...
h0
h1
i0
i1
i2
i3
DAG1
m0 L0
m1 L1
m2 L2
m3 L3
i2
h0s0
h1s1
sM
s0
s1
i4
hMsM
wM
hM
...
buffer h
w0
...
dm(tx_buf + 2) = sr1;
PM
buffer w
...
...
...
{update delay}
...
DM
cdelay(i2, m2);
...
i4
i5
i6
i7
DAG2
m4 L4
m5 L5
m6 L6
m7 L7
dm(tx_buf + 2) = sr1;
The three macros tapin, dot, and cdelay, have been combined into another macro,
cfir.dsp, which implements an FIR filter. The above example would read in this
case:
mx1 = dm(rx_buf + 2);
dm(tx_buf + 2) = sr1;
{placed in DM memory}
i2 = ^w;
L2 = 0;
The post-modify feature is still present, except i2 does not automatically wrap
around upon reaching the end of the buffer. The simulator example delex1.dsp
in the subdirectory examples\sim illustrates the operation of a linear delay line.
EXPERIMENTS
16
.const M = 100;
.var/pm/circ a[M+1], b[M+1];
17
i4 = ^a;
L4 = 2*(M+1);
This declaration defines an extended circular buffer of double-length 2(M+ 1). The
DAG pointer i4 will traverse both buffers a and b before wrapping around to the
beginning of a. See Section 5.7 for more details.
4. Experiments
4.1. Sampling and Quantization
This section contains three types of experiments: (a) sampling and immediate playback (based on the EZ-KIT Lites talkthru program), (b) input quantization effects,
and (c) demonstration of aliasing effects by downsampling.
rti;
.include <c:\adi_dsp\macros\end.dsp>;
{wrapup}
It is the same as the example talkthru program of the EZ-KIT Lite Reference Manual
[2]. The input samples from the codec are deposited in the receive-buffer locations
in DM, rx_buf+1 and rx_buf+2, and then copied into the registers ax1 and mx1 for
the left and right channels.
Then, without any further processing, the input samples are written back into
the transmit-buffer locations tx_buf+1 and tx_buf+2 from where they are read by
the codec and sent to the analog output.
Lab Procedure
Change directory into c:\adi_dsp\examples\quantize by the DOS commands:
Talkthru
This experiment illustrates the sampling and immediate playback of an input audio
signal. The following program, thru.dsp, is simply a copy of the template program,
template.dsp:
{thru.dsp - talkthru program - may be used as template}
{Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996}
{Based on mic2out.dsp from Analog Devices FTP site and the sample talkthru
program of the EZ-KIT Lite Reference Manual.}
{--- define sampling rate in kHz: ----------------------------------------}
{0xc850 = 8
| 0xc851 = 5.5125
| 0xc852 = 16
}
{0xc853 = 11.025 | 0xc854 = 27.42857 | 0xc855 = 18.9 }
.const fs = 0xc85b; {0xc856 = 32
| 0xc857 = 22.05
| 0xc859 = 37.8 }
{0xc85b = 44.1
| 0xc85c = 48
| 0xc85d = 33.075}
{0xc85e = 9.6
| 0xc85f = 6.615
}
{-------------------------------------------------------------------------}
.include <c:\adi_dsp\macros\begin.dsp>;
EXPERIMENTS
Finally, we mention concatenated circular buffers, which can be used to store the
denominator and numerator coefficients of an IIR filter. The following declaration
defines two buffers each of length M+1 in PM:
dsp
cd quantize
Compile, link, and load this program with the DOS commands:
ezk thru
ezl thru
EXPERIMENTS
18
EXPERIMENTS
19
not hear any quantization noise. The noise should become more and more
evident as you decrease B, especially below 8 bits. Make sure you listen to
1-bit and 2-bit speech.
b. The quantization operation is an example of a nonlinear memoryless operation on each input sample. Other examples of such nonlinear operations can
be tried out.
As a distortion example, modify the above program so that instead of quantizing each sample, it squares it, that is, y(n)= x2 (n), and then it sends y(n)
to the output. Compile and run this program. Can you understand the types
of distortions you are hearing in the frequency domain? Repeat when each
input sample is cubed, that is, y(n)= x3 (n). (You may need to scale up the
output y(n) using the shifter.)
Aliasing by Downsampling
sr = ashift mr0 by -L (hi);
sr = ashift sr1 by L (hi);
mr0 = sr1;
{wrapup}
The input samples from the codec are available in 16-bit resolution. In this experiment, each input sample is re-quantized to B-bit resolution, where 1 B 16, and
then it is sent out to the codec. The number of least-significant bits that are thrown
away are L = 16 B.
The re-quantization operation is done conveniently by the shifter by first shifting
to the right by L bits and then shifting to the left by L bits. This has the effect of
filling the last L bits with zeros. The simulator program quantex1.dsp in the
examples subdirectory sim steps through such a re-quantization operation.
Lab Procedure
a. Still in the c:\adi_dsp\examples\quantize directory, compile and run the
above program with an initial choice of B = 6 bits. Speak into the mike and
listen to the quantization noise.
Repeat the above procedure by editing the program and choosing the successive values B = 16, 15, . . . , 1. For the higher values of B you will probably
i5 = ^s; L5 = %s; m5 = 1;
cntr = M;
do zero_s until ce;
zero_s: dm(i5, m5) = 0;
ax0 = A;
dm(s) = ax0;
{set s[0] = A}
{s = [A, 0, ..., 0] = A and M-1 zeros}
EXPERIMENTS
20
mr0 = 0;
mr1 = 0;
{optional amplification}
mr0 = sr1;
mr1 = sr1;
dm(tx_buf + 1) = mr0;
dm(tx_buf + 2) = mr1;
{wrapup}
s(n)= [1, 0, 0, . . . , 0, 1, 0, 0, . . . , 0, 1, 0, 0, . . . , 0, . . . ]
M1
M1
M1
x (n)= s(n)x(n)
T
MT
EXPERIMENTS
21
The first reads the current value of s(n) from the circular buffer and saves it in
ar. The second passes ar back into ar and its only effect is to force the updating
of the flag bits of the ALU, and in particular, the flag AZ, which is AZ=1 whenever
ar=0, and AZ=0 whenever ar is nonzero. The third instruction tests whether ar is
nonzero and if so it outputs the current sample, otherwise it outputs zeros to the
codec. To examine these operations in more detail, look at the simulator program
dnsamp1.dsp in the examples subdirectory sim.
The following program, dnsamp2.dsp, is an alternative way to demonstrate
aliasing. It generates internally a sinusoid of frequency f using a sinusoidal wavetable
generator, and then it outputs every Mth sample of that sinusoid. The resulting sinusoid will be heard as having frequency fa = f modfs , where fs = fs /M.
Wavetables are discussed in Section 8.1.3 of Ref. [1], and also in Section 4.14
of this manual and in the Appendix. The wavetable stores one period of length
D samples of a sinusoid and it is stepped at increments of every c samples. The
resulting sinusoid will have frequency f = cfs /D.
{dnsamp2.dsp - aliasing of a sinusoid by decimation}
{Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996}
{--- define sampling rate in kHz: ----------------------------------------}
{0xc850 = 8
| 0xc851 = 5.5125
| 0xc852 = 16
}
{0xc853 = 11.025 | 0xc854 = 27.42857 | 0xc855 = 18.9 }
.const fs = 0xc850; {0xc856 = 32
| 0xc857 = 22.05
| 0xc859 = 37.8 }
{0xc85b = 44.1
| 0xc85c = 48
| 0xc85d = 33.075}
{0xc85e = 9.6
| 0xc85f = 6.615
}
{-------------------------------------------------------------------------}
.include <c:\adi_dsp\macros\begin.dsp>;
fs =
fs
If M becomes too large, aliasing effects will become audible as the spectral images due to sampling overlap more and more. The aliasing will arise from the spectral components of x(n) that lie outside the reduced Nyquist interval [fs /2, fs /2].
A more mathematical discussion of downsampling effects may be found in Section
12.5 of the text [1].
.const D = 2000;
.var/dm/circ sine[D];
.const FS = 8000;
.const c = 250;
.const T = 4;
.var/dm N;
EXPERIMENTS
22
ax1 = T * FS;
dm(N) = ax1;
{N = T * FS = duration in samples}
{save N in DM}
i5 = ^s; L5 = %s; m5 = 1;
i6 = ^sine; L6 = %sine; m6 = c;
ar = dm(i5, m5);
ar = pass ar;
if ne jump output;
mr1 = 0;
output:
dm(tx_buf + 1) = mr1;
dm(tx_buf + 2) = mr1;
EXPERIMENTS
23
program and listen to the output. This experiment works better if you run it
on a music CD than on your speech.
c. For the program dnsamp2.dsp, the sampling rate is fs = 8 kHz and the frequency of the generated sinusoid f = 1 kHz. If the sampling rate is decimated
down by a factor of M = 10 to the new sampling frequency of fs = 800 Hz,
then the frequency f = 1 kHz will be aliased with fa = 1000 800 = 200 Hz,
which lies in the new Nyquist interval [400, 400] Hz.
To hear this effect, first you must generate the 2000-long period of the wavetable.
The program reads the sinusoidal table values in 1.15 hex format from the file
sinetbl.hex. These values can be generated by the following DOS command,
which pipes the output of sinetbl.exe into dec2hex.exe:
sinetbl 0 1 2000 | dec2hex 1.15 > sinetbl.hex
The program plays the output for 4 seconds. No microphone input is required.
The codec keeps sampling the input and, therefore, interrupts the processor
at the sampling rate fs . However, the samples that are loaded automatically
into the receive register rx_buf are not used. Instead, the interrupt service
routine reads a wavetable sample and sends it to the output every Mth time,
while at other times, it sends zero to the output.
In this experiment, first choose M = 1, compile, and run the program to hear
the 1 kHz sinusoid. Then, set M = 10, recompile and run to hear the 200 Hz
aliased version.
4.2. Delays
The program delay.dsp implements a maximum delay of duration TD = 0.75 sec.
At an 8 kHz sampling rate, the total number of samples in the delay will be:
D=
rti;
stop:
.include <c:\adi_dsp\macros\end.dsp>;
{wrapup}
Lab Procedure
a. The constant A represents the unit amplitude of the re-sampling pulse train
s(n). Set A = 0 and M = 1, and recompile and run. You should hear nothing
because s(n) becomes identically zero, so that ar remains zero causing only
zeros to be sent to the codec. For the next part, reset A to unity.
b. With an initial choice of fs = 48 kHz, choose successive downsampling ratios of M = 1, 2, 3, 4, 6, 8, 12, 16, 24, 48, 96, corresponding to sample rates of
fs = 48, 24, 16, 12, 8, 6, 4, 3, 2, 1, 0.5 kHz. In each case, recompile and run the
TD
= fs TD = 8000 Hz 0.75 sec = 6000
T
Thus, the transfer function of the delay will be H(z)= zD = z6000 . Its implementation requires a 6001-dimensional circular buffer:
{delay.dsp - plain delay by TD sec}
{Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996}
{Based on cdelay.c of Introduction to Signal Processing, p.177.
I/O equation: y(n) = x(n-d), range of delay d=1, 2, ..., D.
Sample processing algorithm:
for each input x do:
y = tap(D, w, p, d)
get d-th tap
*p = x
put input x into tap-0
cdelay(D, w, &p)
update delay
}
{--- define sampling rate in kHz: ----------------------------------------}
{0xc850 = 8
| 0xc851 = 5.5125
| 0xc852 = 16
}
EXPERIMENTS
24
i2 = ^w;
L2 = %w;
{--- sample processing algorithm --- process right channel only ----------}
tap(i2, m2, d, my1);
tapin(i2, m2, mx1);
cdelay(i2, m2);
EXPERIMENTS
25
Lab Procedure
a. Go to the directory c:\adi_dsp\examples\delay and compile, link, and load
this program by the following DOS commands:
dsp
cd delay
ezk delay
ezl delay
Give the system an impulse by lightly tapping the table with the mike, and
listen to the impulse response. Then, speak into the mike.
Bring the mike near the speaker and then give the system an impulse. You
should hear repeated echoes. If you bring the mike too close to the speakers the output goes unstable. Draw a block diagram realization that would
explain the effect you are hearing. Experimentally determine the distance at
which the echoes remain marginally stable, that is, neither die out nor diverge. (Technically speaking, the poles of your closed-loop system lie on the
unit circle.)
b. Change the sampling rate to 16 kHz, recompile and reload keeping the value
of d the same, that is, d = 2000. Listen to the impulse response. What is the
duration of the delay in seconds now?
c. Reset the sampling rate back to 8 kHz, and this time change d to its maximum
value d = D = 6000. Recompile, reload, and listen to the impulse response.
Experiment with lower and lower values of d and listen to your delayed voice
until you can no longer distinguish a separate echo. How many milliseconds
of delay does this correspond to?
d. Set d = 0, recompile and reload. It should correspond to no delay at all, that
is, y(n)= x(n). But what do you hear? Can you explain why? Can you fix it by
changing the program? Will your modified program still work with d = 0? Is
there any good reason for structuring the program the way it was originally?
.include <c:\adi_dsp\macros\end.dsp>;
{wrapup}
The initial version of the program outputs the 2000th tap, that is, the signal y(n)=
x(nd), corresponding to a time delay in seconds: Td = dT = d/fs = 2000/8000 =
0.25 sec. The sample processing algorithm in the notation of the text [1] is:
d delays
x
s0
-1
-1
...
s1 z s2
z-1 s ...
d
D delays
z-1 s
D
p = x
cdelay(D, w, & p)
The implementation of an FIR filter is accomplished with the help of the macro cfir,
which is the assembly code equivalent of the text routines cfir.c and cfir2.c.
The following is a general FIR filtering program:
{fir.dsp - FIR filter experiment}
{Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996}
{Based on cfir.c or cfir2.c of Introduction to Signal Processing}
{--- define sampling rate in kHz: ----------------------------------------}
{0xc850 = 8
| 0xc851 = 5.5125
| 0xc852 = 16
}
EXPERIMENTS
26
i2 = ^w; L2 = %w;
i4 = ^h; L4 = %h;
.init h: <fir.hex>;
EXPERIMENTS
27
fs = 8000;
% 8 kHz rate
Apass = 0.1;
Astop = 40;
% 0.1 dB
% 40 dB, stopband ripple: delta = 0.01
);
);
% e.g., fc = 200 Hz
% e.g., Df = 100 Hz
fpass = fc - Df/2;
fstop = fc + Df/2;
h = klh(1, fs, fpass, fstop, Apass, Astop);
% Kaiser design
% filter order M
NF = 400;
w = (0:NF-1) * pi / NF;
H = 20 * log10(abs(dtft(h, w)));
% number of frequencies
% NF freqs over positive Nyquist
% magnitude response in dB
Using this program, we have designed two FIR filters with a sampling rate of 8 kHz
and cutoff frequencies and widths:
f
100 Hz
10 Hz
These specifications imply that the passband, transition, and stopband frequency
ranges are in Hz (where the stopband extends to the Nyquist frequency fs /2 = 4000
Hz):
{wrapup}
The filter coefficients are read from the hex file fir.hex. The value of the filter
order M must be edited into the program. The delay-line buffer is in DM memory
and the filter coefficient buffer in PM.
The following MATLAB file firlp.m implements a Kaiser design of a lowpass
filter with prescribed cutoff frequency and transition width. The passband and
stopband attenuations are assumed to be 0.1 dB and 40 dB, respectively. It uses
the functions dtft.m and klh.m of Chapter 10 of the text [1]:
% firlp.m - lowpass FIR design
fc
200 Hz
200 Hz
passband
0150
0195
transition
150250
195205
stopband
2504000
2054000
Thus, within the passband, the attenuation must remain less than 0.1 dB and within
the stopband, it must be greater than 40 dB. The magnitude responses |H(f )| of
the two filters are plotted in units of dB, that is, 20 log10 |H(f )|, in the following
figures:
EXPERIMENTS
28
EXPERIMENTS
29
.init h: <fir.hex>;
The designed filters have orders M = 206 and M = 2054, respectively, and their
impulse responses are in the data files lp1.dec and lp2.dec in decimal format (as
generated by firlp.m.)
As discussed in Ch.10 of the text [1] that the filter order M of an FIR filter is
essentially inversely proportional to its transition width f , and therefore, it should
be no surprise that the second filter has 10-times greater order than the first.
{--- sample processing algorithm --- process right input only ------------}
cfir(M, i4, m4, i2, m2, ax1);
ay1 = mr1;
cfir(M, i4, m4, i3, m3, mx1);
my1 = mr1;
dm(tx_buf + 1) = ay1;
dm(tx_buf + 2) = my1;
{wrapup}
Finally, we consider a third version of the FIR filtering program, fir3.dsp, that
uses the concept of wavetable generators from Section 8.1.3 of the text [1].
.include <c:\adi_dsp\macros\begin.dsp>;
i2 = ^wL; L2 = %wL;
i3 = ^wR; L3 = %wR;
i4 = ^h; L4 = %h;
EXPERIMENTS
30
.const M = 206;
.var/dm/circ w[M+1];
.var/pm/circ h[M+1];
.const D = 800;
.var/dm/circ s[D];
EXPERIMENTS
31
dm(tx_buf + 1) = mr1;
dm(tx_buf + 2) = mr1;
rti;
.const
.const
.const
.const
c1
c2
A1
A2
=
=
=
=
10;
40;
0x4000;
0x4000;
.const T = 4;
.var/dm N;
ax1 = T * FS;
dm(N) = ax1;
{N = T * FS = duration in samples}
{save N in DM}
i2 = ^w; L2 = %w;
i4 = ^h; L4 = %h;
.init h: <fir.hex>;
{read
{from
{load
{from
.init s: <sinetbl.hex>;
stop:
.include <c:\adi_dsp\macros\end.dsp>;
{wrapup}
This program generates internally a sum of two sinusoids and filters them through
the cfir macro. The input signal is:
f1 = c1
fs
,
D
f2 = c2
fs
D
The wavetable is filled with one period of length D, that is, by the numbers:
s(n)= A sin
2n
D
n = 0, 1, . . . , D 1
mx1 = A1;
my1 = dm(i5, m5);
mr = mx1 * my1 (ss);
{my1 = s1 = sin(2*pi*f1*t)}
{mr = A1 * s1}
mx1 = A2;
my1 = dm(i6, m6);
mr = mr + mx1 * my1 (rnd);
{my1 = s2 = sin(2*pi*f2*t)}
{mr = x = A1 * s1 + A2 * s2}
#if 1
cfir(M, i4, m4, i2, m2, mr1);
#endif
The wavetable is loaded on the chip at run time. The wavetable is cycled over
at different speeds by two independent DAG2 pointers, i5 and i6, which are incremented respectively by m5=c1 and m6=c2. The wavetable increments are c1 = 10
and c2 = 40, resulting in the frequencies f1 = 100 and f2 = 400 Hz. One is in the
passband and the other in the stopband of the filter. Thus, the filter will remove
f2 and let f1 pass through. The amplitudes were chosen to be A1 = A2 = 0.5. In
Section 4.14, we discuss the definition and use of the macro wavgen.dsp which can
also be used to generate the required sinusoids.
Lab Procedure
a. Go into the directory c:\adi_dsp\examples\fir. Then, convert the filter
coefficient file lp1.dec into the hex file fir.hex. Then, compile and load the
fir.dsp program. These operations are carried out by the commands:
dsp
cd fir
dec2hex 1.15 < lp1.dec > fir.hex
ezk fir
ezl fir
EXPERIMENTS
32
Speak into the mike or sing a do-re-mi scale. Note how the filter cuts off the
higher pitches of your voice.
EXPERIMENTS
z-D
s1
Speak into the mike. The filter will cut off the higher pitches in your voice,
but it also introduces a perceptible delay in the output. This delay was too
short to be heard for the first filter.
D=
TD =
M
2
T=
M
2fs
Thus, how much is the delay in seconds for filter-1 and filter-2?
c. Next, run program fir3.dsp with the order-206 filter. First, listen to the
unfiltered sinusoid f1 . This can be done by commenting out the cfir call and
replacing the sinusoid amplitudes by A1 = 1 (0x7fff in hex) and A2 = 0.
Then, listen to the unfiltered component f2 , and then, to the unfiltered sum
of both. Finally, uncomment cfir and send in the sum of the two. You will
hear only the f1 component at the output.
s0
b. Next, test the longer filter. First edit the file fir.dsp so that M = 2054, then
convert the coefficient file lp2.dec into fir.hex, recompile and reload.
Through their phase response, all filters introduce a certain amount of delay,
which depends on the frequency of the input. FIR filters can be designed to
have linear phase, which implies that all frequency components of the input
get delayed by the same amount, and thus, the input as a whole gets delayed.
For a linear phase FIR filter of order M, the amount of delay is:
33
z-D
s2
z-D
s3
s0 = x
s1 = tap(3D, w, p, D)
s2 = tap(3D, w, p, 2D)
s3 = tap(3D, w, p, 3D)
y = s0 + as1 + a2 s2 + a3 s3
p = s0
cdelay(3D, w, & p)
a2
a3
This filter can be implemented using the program fir.dsp of the previous experiment as a general FIR filter with an impulse response:
h = [1, 0, 0, . . . , 0, a, 0, 0, . . . , 0, a2 , 0, 0, . . . , 0, a3 ]
D1 zeros
D1 zeros
D1 zeros
.const
.const
.const
.const
a0
a1
a2
a3
=
=
=
=
0x7fff;
0x4000;
0x2000;
0x1000;
{a0
{a1
{a2
{a3
=
=
=
=
.const D = 2000;
.var/dm/circ w[3*D + 1];
i2 = ^w;
L2 = %w;
EXPERIMENTS
34
{--- sample processing algorithm --- process right channel only ----------}
my1 = a0;
mr = mx1 * my1 (ss);
tap(i2, m2, D, my0);
mx0 = a1;
mr = mr + mx0 * my0 (ss);
tap(i2, m2, 2*D, my0);
mx0 = a2;
mr = mr + mx0 * my0 (ss);
{mr = a0 * x}
EXPERIMENTS
35
Listen to the impulse response of the filter. Speak into the mike. Bring the
mike close to the speakers and get a closed-loop feedback.
b. Keeping the delay D the same, choose a = 0.2 and run the program again.
What effect do you hear? Repeat for a = 0.1.
c. Finally, run the program with the values a = 1 and a = 1. Note that 1 is represented approximately by 0x7fff in 1.15 format, whereas 1 is represented
exactly by 0x8000.
d. The FIR comb filter can also be implemented as an ordinary FIR filter, without
taking into account the sparseness of its impulse response h. In this part,
define the (3D + 1)-dimensional impulse response:
h = [1, 0, 0, . . . , 0, a, 0, 0, . . . , 0, a2 , 0, 0, . . . , 0, a3 ]
{mr = a0 * x + a1 * s1}
D1 zeros
D1 zeros
D1 zeros
and assign it to a circular buffer in PM. Then, use the program fir.dsp of
the previous section to implement this filter. Compile and run with the value
D = 2000 so that you may compare its output with that of comb.dsp.
e. The FIR comb can also be implemented recursively using the geometric series
formula to rewrite its transfer function in the recursive form:
1 a4 z4D
1 azD
which requires a (4D+1)-dimensional delay-line buffer. The canonical realization and the corresponding sample processing algorithm are shown below:
s0
rti;
-D
.include <c:\adi_dsp\macros\end.dsp>;
{wrapup}
s1
z-3D
s4
a4
s0 = x + as1
y = s0 a4 s4
p = s0
cdelay(4D, w, & p)
The following program comb2.dsp is the assembly code implementation. Using the values D = 1600 (corresponding to a 0.2 sec delay) and a = 0.5,
recompile and run both the comb.dsp and comb2.dsp programs and listen
to their outputs. In general, such recursive implementations of FIR filters are
more prone to the accumulation of roundoff errors than the non-recursive
versions. You may want to run these programs with a = 1 and a = 1 to
observe this sensitivity.
EXPERIMENTS
36
EXPERIMENTS
37
mx0 = a4;
tap(i2, m2, 4*D, my0);
mr = mr - mx0 * my0 (rnd);
if mv sat mr;
{wrapup}
.const D = 1600;
.var/dm/circ w[4*D + 1];
i2 = ^w;
L2 = %w;
A plain reverberator can be used as an elementary building block for more complicated reverberation algorithms. It is given by Eq. (8.2.12) of the text [1] and shown
in Fig. 8.2.6. Its I/O equation and transfer function are:
{--- sample processing algorithm --- process right channel only ----------}
mr = 0;
mr1 = mx1;
tap(i2, m2, D, my0);
mx0 = a1;
mr = mr + mx0 * my0 (rnd);
if mv sat mr;
sr0 = mr1;
{mr = x = input}
{my0 = s1 = D-th tap}
{mr = s0 = x + a1 * s1}
{saturate mr if overflow}
{sr0 = s0}
1
1 azD
Its sample processing algorithm using a circular delay-line buffer is given by Eq. (8.2.14)
of Ref. [1]:
H(z)=
z-D
sD
a
y = x + asD
p = y
cdelay(D, w, & p)
EXPERIMENTS
38
{0xc850 = 8
| 0xc851 = 5.5125
| 0xc852 = 16
}
{0xc853 = 11.025 | 0xc854 = 27.42857 | 0xc855 = 18.9 }
.const fs = 0xc850; {0xc856 = 32
| 0xc857 = 22.05
| 0xc859 = 37.8 }
{0xc85b = 44.1
| 0xc85c = 48
| 0xc85d = 33.075}
{0xc85e = 9.6
| 0xc85f = 6.615
}
{-------------------------------------------------------------------------}
.include <c:\adi_dsp\macros\begin.dsp>;
EXPERIMENTS
39
.const a = 0x4000;
.const D = 3000;
.var/dm/circ w[D+1];
i2 = ^w;
.include <c:\adi_dsp\macros\begin.dsp>;
L2 = %w;
.const aL = 0x4000;
.const DL = 3000;
.const aR = 0x4000;
.const DR = 3000;
.var/dm/circ wL[DL+1];
.var/dm/circ wR[DR+1];
i2 = ^wL; L2 = %wL;
i3 = ^wR; L3 = %wR;
{--- sample processing algorithm --- process right channel only ----------}
mr = 0;
mr1 = mx1;
{mr = x = input}
my1 = a;
tap(i2, m2, D, mx1);
mr = mr + mx1 * my1 (rnd);
{put y in tap-0}
{update delay}
my0 = aL;
tap(i2, m2, DL, mx0);
mr = mr + mx0 * my0 (rnd);
{put y in tap-0}
{update left delay}
{wrapup}
EXPERIMENTS
40
EXPERIMENTS
41
D 2D,
my0 = aR;
tap(i3, m3, DR, mx0);
mr = mr + mx0 * my0 (rnd);
{put y in tap-0}
{update right delay}
dm(tx_buf + 2) = mr1;
a a2
Test if this is true by running your program and hearing the output with
D = 6000 and a = 0.52 = 0.25 and comparing it with the caseD = 3000 and
a = 0.5. Repeat the comparison also with D = 1500 and a = 0.5 = 0.7071.
rti;
.include <c:\adi_dsp\macros\end.dsp>;
{wrapup}
The plain reverberator is an IIR comb filter with frequency response shown in Fig.
8.2.7. When the filter parameter a is positive and near unity, the peak gains 1/(1a)
become large, causing overflows. In such cases, the input must be appropriately
scaled down using the shifter before it is passed to the filter. This was not done in
the above programs, but the reader should be aware that it may need to be done.
Lab Procedure
(If you have a portable CD with you, change the jumpers on the EZ-KIT Lite
board, and play a CD through it. To get better sound quality, you may want
to use the stereo version plain2.dsp. In this case, you may also want to
experiment with using different values of the left and right delays.)
b. Recompile and run the program with the new feedback coefficient a = 0.25.
Listen to the impulse response. Repeat for a = 0.75. Discuss the effect of
increasing or decreasing a.
c. According to Eq. (8.2.16), the effective reverberation time constant is given by
eff =
ln
TD ,
ln a
TD = DT = D/fs
a + zD
1 azD
Its sample processing algorithm using a circular delay-line buffer is given by Eq. (8.2.14)
of Ref. [1]:
-a
s0
a. Go to directory c:\adi_dsp\examples\plain, compile, link, and run the program plain.dsp with the parameter values D = 3000 and a = 0.5. Listen to
the impulse response of the system. Speak into the mike.
H(z)=
z-D
sD
s0 = x + asD
y = as0 + sD
p = s0
cdelay(D, w, & p)
EXPERIMENTS
42
.include <c:\adi_dsp\macros\begin.dsp>;
EXPERIMENTS
sD = tap(D, w, p, D)
s0 = x + a * sD
y = -a * s0 + sD
*p = s0
celay(D, w, &p)
i2 = ^w;
L2 = %w;
43
}
{--- define sampling rate in kHz: ----------------------------------------}
{0xc850 = 8
| 0xc851 = 5.5125
| 0xc852 = 16
}
{0xc853 = 11.025 | 0xc854 = 27.42857 | 0xc855 = 18.9 }
.const fs = 0xc850; {0xc856 = 32
| 0xc857 = 22.05
| 0xc859 = 37.8 }
{0xc85b = 44.1
| 0xc85c = 48
| 0xc85d = 33.075}
{0xc85e = 9.6
| 0xc85f = 6.615
}
{-------------------------------------------------------------------------}
.include <c:\adi_dsp\macros\begin.dsp>;
{--- sample processing algorithm --- process right channel only ----------}
mr = 0;
mr1 = mx1;
{mr = x = input}
my0 = a;
tap(i2, m2, D, mx0);
mr = mr + mx0 * my0 (rnd);
{put s0 in tap-0}
{update delay}
{mr = a * s0}
{ay0 = sD}
{ar = y = sD - a * s0}
.const aL = 0x4000;
.const DL = 3000;
.const aR = 0x4000;
.const DR = 3000;
.var/dm/circ wL[DL+1];
.var/dm/circ wR[DR+1];
i2 = ^wL; L2 = %wL;
i3 = ^wR; L3 = %wR;
{wrapup}
mr = 0;
mr1 = ax1;
my0 = aL;
tap(i2, m2, DL, mx0);
mr = mr + mx0 * my0 (rnd);
{put s0 in tap-0}
{update left delay}
mr =
{mr = a * s0}
EXPERIMENTS
44
ay0 = mx0;
ar = ay0 - mr1;
{ay0 = sD}
{ar = y = sD - a * s0}
dm(tx_buf + 1) = ar;
EXPERIMENTS
45
*p = s0
celay(D, w, &p)
my0 = aR;
tap(i3, m3, DR, mx0);
mr = mr + mx0 * my0 (rnd);
{put s0 in tap-0}
{update right delay}
{mr = a * s0}
{ay0 = sD}
{ar = y = sD - a * s0}
dm(tx_buf + 2) = ar;
i2 = ^w;
L2 = %w;
{wrapup}
input_samples: ena sec_reg;
{--- sample processing algorithm --- process right channel only ----------}
mr = 0;
y
sD
y = sD ax
p = s0 = x + ay
cdelay(D, w, & p)
z-D
s0
a
my1 = a;
mr = mr - mx1 * my1 (rnd);
{mr = y = sD - a * x}
sr1 = mr1;
{sr1 = y}
mr = 0;
mr1 = mx1;
{mr = x}
{mr = s0 = x + a * y}
{put s0 in tap-0}
{update delay}
EXPERIMENTS
46
EXPERIMENTS
47
}
{--- return from interrupt -----------------------------------------------}
{--- define sampling rate in kHz: ----------------------------------------}
{0xc850 = 8
| 0xc851 = 5.5125
| 0xc852 = 16
}
{0xc853 = 11.025 | 0xc854 = 27.42857 | 0xc855 = 18.9 }
.const fs = 0xc850; {0xc856 = 32
| 0xc857 = 22.05
| 0xc859 = 37.8 }
{0xc85b = 44.1
| 0xc85c = 48
| 0xc85d = 33.075}
{0xc85e = 9.6
| 0xc85f = 6.615
}
{-------------------------------------------------------------------------}
rti;
.include <c:\adi_dsp\macros\end.dsp>;
{wrapup}
Lab Procedure
a. Go to directory c:\adi_dsp\examples\allpass, compile, link, and run the
program allpass.dsp with the parameter values D = 3000 and a = 0.5.
Repeat with a = 0.9.
Listen to the impulse response of the system. Speak into the mike. Compare
the output sound with that of the plain reverberator.
b. As in the previous experiment, compare the 60-dB time constants of the case
D = 6000 and a = 0.52 and the case D = 3000 and a = 0.5.
c. Repeat part (a) using the transposed form implemented by the program allpass3.dsp. Compare the output to that of allpass.dsp.
v0 = av1 + sD
u = b0 v0 + b1 v1
y =x+u
v1 = v0
p = y
cdelay(D, w, & p)
.include <c:\adi_dsp\macros\begin.dsp>;
.const a = 0x4000;
.const b0 = 0x199a;
.const b1 = 0x0ccd;
{a = 0.50}
{b0 = 0.20}
{b1 = 0.10}
.const D = 3000;
.var/dm/circ w[D+1];
i2 = ^w;
L2 = %w;
ax0 = 0;
dm(v1) = ax0;
zero(i2, m2, L2);
{--- sample processing algorithm --- process right channel only ----------}
tap(i2, m2, D, mr1);
mx0 = a;
my1 = dm(v1);
mr = mr + mx0 * my1 (rnd);
if mv sat mr;
sr0 = mr1;
my0 = b0;
mr = mr1 * my0 (ss);
mx0 = b1;
mr = mr + mx0 * my1 (rnd);
if mv sat mr;
ay1 = mx1;
ar = mr1 + ay1;
{state v1}
{mr = v0 = sD + a * v1}
{save v0 for later updating v1}
{mr = b0 * v0}
{mr = u = b0 * v0 + b1 * v1}
{ay1 = x = input}
{ar = y = x + u = output}
EXPERIMENTS
dm(v1) = sr0;
tapin(i2, m2, ar);
cdelay(i2, m2);
48
{wrapup}
Lab Procedure
a. Go to the directory c:\adi_dsp\examples\lowpass. Using the parameters
D = 3000, a = 0.5, b0 = 0.20, b1 = 0.10, compile and run this program.
Listen to its impulse response. Speak into the mike. Notice how successive
echoes get more and more mellow as they circulate through the lowpass filter.
b. Try the case D = 20, a = 0, b0 = b1 = 0.49. You will hear a guitar-like sound.
Repeat for D = 40 and D = 80. What do you hear? We will encounter these
cases again in making a model of a guitar string.
EXPERIMENTS
49
.const
.const
.const
.const
b1
b2
b3
b4
=
=
=
=
0x7fff;
0x7333;
0x6666;
0x599a;
{b1
{b2
{b3
{b4
=
=
=
=
0.9999}
0.90}
0.80}
0.70}
.const
.const
.const
.const
.const
.const
a1
a2
a3
a4
a5
a6
=
=
=
=
=
=
0x70a4;
0x70a4;
0x70a4;
0x70a4;
0x70a4;
0x70a4;
{a1
{a2
{a3
{a4
{a5
{a6
=
=
=
=
=
=
0.88}
0.88}
0.88}
0.88}
0.88}
0.88}
.const
.const
.const
.const
.const
.const
D1
D2
D3
D4
D5
D6
=
=
=
=
=
=
1759;
1949;
2113;
2293;
307;
313;
.var/dm/circ
.var/dm/circ
.var/dm/circ
.var/dm/circ
.var/dm/circ
.var/dm/circ
w1[D1
w2[D2
w3[D3
w4[D4
w5[D5
w6[D6
+
+
+
+
+
+
.var/dm x[5];
i2
i3
i4
i5
i6
i7
=
=
=
=
=
=
^w1;
^w2;
^w3;
^w4;
^w5;
^w6;
zero(i2,
zero(i3,
zero(i4,
zero(i5,
zero(i6,
zero(i7,
L2
L3
L4
L5
L6
L7
m2,
m3,
m4,
m5,
m6,
m7,
1];
1];
1];
1];
1];
1];
=
=
=
=
=
=
%w1;
%w2;
%w3;
%w4;
%w5;
%w6;
L2);
L3);
L4);
L5);
L6);
L7);
.include <c:\adi_dsp\macros\begin.dsp>;
{--- sample processing algorithm --- process right channel only ----------}
si = mx1;
sr = ashift si by -3 (hi);
dm(x) = sr1;
EXPERIMENTS
50
EXPERIMENTS
51
mr = 0;
mx0 = b1; my0 = dm(x+1);
mr = mr + mx0 * my0 (ss);
{mr = b1 * x1}
{mr = b1 * x1 + b2 * x2}
{mr = b1 * x1 + b2 * x2 + b2 * x2}
{mr = x = input}
mx1 = a1;
tap(i2, m2, D1, my1);
mr = mr + mx1 * my1 (rnd);
dm(x + 1) = mr1;
mr = 0;
mr1 = dm(x);
{mr = x = input}
my0 = a5;
tap(i6, m6, D5, mx0);
mr = mr + mx0 * my0 (rnd);
mx1 = a2;
tap(i3, m3, D2, my1);
mr = mr + mx1 * my1 (rnd);
{mr = a5 * s05}
{ay0 = sD5}
{ar = x6 = sD5 - a5 * s05}
dm(x + 2) = mr1;
mr = 0;
mr1 = ar;
{mr = x6 = input}
my0 = a6;
tap(i7, m7, D6, mx0);
mr = mr + mx0 * my0 (rnd);
{mr = a6 * s06}
{ay0 = sD6}
{ar = y = sD6 - a6 * s06}
{mr = x = input}
mx1 = a3;
tap(i4, m4, D3, my1);
mr = mr + mx1 * my1 (rnd);
dm(x + 3) = mr1;
{mr = x = input}
mx1 = a4;
tap(i5, m5, D4, my1);
mr = mr + mx1 * my1 (rnd);
dm(x + 4) = mr1;
dm(tx_buf + 1) = ar;
dm(tx_buf + 2) = ar;
.include <c:\adi_dsp\macros\end.dsp>;
{wrapup}
There are six multiple delays each requiring its own circular buffer and DAG pointer.
EXPERIMENTS
52
Lab Procedure
EXPERIMENTS
53
GL (z)= aL ,
GR (z)= aR
Each channel has its own delay-line buffer and circular pointer. The sample
processing algorithm is modified now to take in a pair of stereo inputs and produce
a pair of stereo outputs:
for each input stereo pair xL , xR do:
sLL = tap(L, wL , pL , L)
sRR = tap(R, wR , pR , R)
yL = cL xL + sLL
yR = cR xR + sRR
pL = sL0 = bL xL + aL sLL + dR sRR
pR = sR0 = bR xR + aR sRR + dL sLL
cdelay(L, wL , & pL )
cdelay(R, wR , & pR )
where L and R denote the left and right delays. Cross-coupling between the channels arises because of the coefficients dL and dR . The following program stereo.dsp
is an implementation:
.const bL = 0x6666;
.const bR = 0x6666;
.const cL = 0x4000;
.const cR = 0x4000;
.const dL = 0x4000;
.const dR = 0x4000;
.const L = 3000;
.const R = 3000;
.var/dm/circ wL[L+1];
.var/dm/circ wR[R+1];
i2 = ^wL; L2 = %wL;
i3 = ^wR; L3 = %wR;
EXPERIMENTS
54
EXPERIMENTS
aR = 0, you will hear repeated echoes bouncing back and forth between the
speakers because of the cross-coupling.
{mr1 = sLL}
55
H(z)= c + b
{send left output to codec}
{mr1 = sRR}
Its block diagram realization and corresponding sample processing algorithm using
a circular delay-line buffer are given below:
zD
1 azD
s0
z -D
sD
{mr = bL * xL}
y = cx + sD
p = s0 = bx + asD
cdelay(D, w, & p)
{mr = bL * xL + aL * sLL}
{mr = bR * xR}
{mr = bR * xR + aR * sRR}
{mr = bR * xR + aR * sRR + dL * sLL}
{input to right delay}
{update right delay}
{wrapup}
Lab Procedure
a. Go to the directory c:\adi_dsp\examples\stereo. Compile and run this
program. Even though the self-feedback multipliers were set to zero aL =
EXPERIMENTS
56
.const a = 0x4000;
.const b = 0x7fff;
.const c = 0x0000;
{a = 0.5}
{b = 0.9999}
{c = 0}
.const D = 6000;
.var/dm/circ w[D+1];
i2 = ^w;
L2 = %w;
{feedback gain}
{gain before delay}
{gain for direct sound}
{--- sample processing algorithm --- process right channel only ----------}
tap(i2, m2, D, sr1);
mr1 = sr1;
my1 = c;
mr = mr + mx1 * my1 (rnd);
if mv sat mr;
ar = mr1;
my1 = b;
mr = mx1 * my1 (ss);
my1 = a;
mr = mr + sr1 * my1 (rnd);
if mv sat mr;
tapin(i2, m2, mr1);
cdelay(i2, m2);
57
clearly heard.
b. What values of b and c would you use (expressed in terms of a) in order to
implement a plain reverberator of the form:
H(z)=
1
1 azD
For a = 0.5, calculate the proper values of b, c, and then compile and run the
program. Compare its output with that of plain.dsp.
c. Compile and run the cases a = 1, b = c = 1 and a = 1, b = 1, c = 1. What
are the transfer functions in these cases?
{mr = y = sD + c * x}
{save for output}
{mr = b * x}
{mr = s0 = b * x + a * sD}
EXPERIMENTS
{sr1 = sD}
{wrapup}
Lab Procedure
a. Go to the directory c:\adi_dsp\examples\revdel. Compile and run this
program. Listen to its impulse response and speak into the mike. Here, the
direct sound path has been removed, c = 0, in order to let the echoes be more
y = b0 x + b1 s1D1 + b2 s2D2
p2 = s20 = s1D1 + a2 s2D2
cdelay(D2 , w2 , & p2 )
p1 = s10 = x + a1 s1D1
cdelay(D1 , w1 , & p1 )
EXPERIMENTS
58
y = b0 * x + b1 * s1D + b2 * s2D
*p2 = s20 = s1D + a2 * s2D
cdelay(D2, w2, &p2)
*p1 = s10 = x + a1 * s1D
cdelay(D1, w1, &p1)
filter output
put input s20 into delay-2
update delay-2
put input s10 into delay-1
update delay-1
EXPERIMENTS
59
{mr = b0 * x + b1 * s1D}
my0 = b2;
mr = mr + sr1 * my0 (rnd);
if mv sat mr;
}
{--- write output samples to codec ---------------------------------------}
{--- define sampling rate in kHz: ----------------------------------------}
{0xc850 = 8
| 0xc851 = 5.5125
| 0xc852 = 16
}
{0xc853 = 11.025 | 0xc854 = 27.42857 | 0xc855 = 18.9 }
.const fs = 0xc850; {0xc856 = 32
| 0xc857 = 22.05
| 0xc859 = 37.8 }
{0xc85b = 44.1
| 0xc85c = 48
| 0xc85d = 33.075}
{0xc85e = 9.6
| 0xc85f = 6.615
}
{-------------------------------------------------------------------------}
.include <c:\adi_dsp\macros\begin.dsp>;
{a1 = 0.50}
{a2 = 0.40}
.const b0 = 0x7fff;
.const b1 = 0x6666;
.const b2 = 0x4ccd;
.const D1 = 3000;
.const D2 = 4000;
.var/dm/circ w1[D1+1];
.var/dm/circ w2[D2+1];
{delay-1 buffer}
{delay-2 buffer}
i2 = ^w1; L2 = %w1;
i3 = ^w2; L3 = %w2;
{clear delay-1}
{clear delay-2}
dm(tx_buf + 1) = mr1;
dm(tx_buf + 2) = mr1;
{mr = s1D}
{mr = s20 = s1D + a2 * s2D}
{put s20 into tap-0 of delay-2}
{update delay-2}
{mr = x}
{mr = s10 = x + a1 * s1D}
{put s10 into tap-0 of delay-1}
{update delay-1}
.include <c:\adi_dsp\macros\end.dsp>;
{wrapup}
Lab Procedure
a. Go to the directory c:\adi_dsp\examples\multidel. Compile and run this
program. Listen to its impulse response and speak into the mike.
b. Set b1 = 0 and run again. Then, set b2 = 0 and run. Can you explain what
you hear?
{--- sample processing algorithm --- process right channel only ----------}
tap(i2, m2, D1, sr0);
tap(i3, m3, D2, sr1);
my0 = b0;
mr = mx1 * my0 (ss);
{mr = b0 * x}
my0 = b1;
EXPERIMENTS
60
s0
b0
y
z-D1
s1
a1
b1
z-D2
a2
s2
EXPERIMENTS
61
.const a1 = 0x199a;
.const a2 = 0x4000;
{a1 = 0.20}
{a2 = 0.50}
.const b0 = 0x7fff;
.const b1 = 0x6666;
.const b2 = 0x4ccd;
.const D1 = 3000;
.const D2 = 4500;
.var/dm/circ w[D1+D2+1];
{delay-line buffer}
i2 = ^w; L2 = %w;
b2
y = b0 x + b1 s1 + b2 s2
s0 = x + a1 s1 + a2 s2
p = s0
cdelay(D1 + D2 , w, & p)
{--- sample processing algorithm --- process right channel only ----------}
tap(i2, m2, D1, sr0);
tap(i2, m2, D1+D2, sr1);
my0 = b0;
mr = mx1 * my0 (ss);
my0 = b1;
mr = mr + sr0 * my0 (ss);
my0 = b2;
mr = mr + sr1 * my0 (rnd);
if mv sat mr;
{mr = b0 * x}
{mr = b0 * x + b1 * s1}
{mr = y = b0 * x + b1 * s1 + b2 * s2}
mr = 0;
mr1 = mx1;
my0 = a1;
mr = mr + sr0 * my0 (ss);
my0 = a2;
mr = mr + sr1 * my0 (rnd);
if mv sat mr;
{mr = x}
{mr = x + a1 * s1}
{mr = s0 = x + a1 * s1 + a2 * s2}
EXPERIMENTS
62
{wrapup}
EXPERIMENTS
63
Lab Procedure
a. Go to the directory c:\adi_dsp\examples\multitap. Compile and run this
program. Listen to its impulse response and speak into the mike.
b. Repeat for the following values of the feedback parameters: a1 = a2 = 0.5,
which makes the system marginally stable with a periodic steady output (any
random noise would be grow unstable.)
Repeat also for the case a1 = a2 = 0.75, which corresponds to an unstable
filter. (Please reset the processor before the output grows too loud.)
.var/dm v1;
.const a = 0x0000;
.const b0 = 0x3fdf;
.const b1 = 0x3fdf;
{a = 0}
{b0 = 0.499}
{b1 = 0.499}
.const D = 100;
.var/dm/circ w[D+1];
i2 = ^w;
L2 = %w;
.init w: <w.hex>;
G(z)= b0 (1 + z1 )
with some b0 0.5 to improve the stability of the closed-loop system. See text references [108-111] for more discussion on such models. The following program guitar.dsp implements the algorithm. The code is identical to that of lowpass.dsp:
{guitar.dsp - Karplus-Strong string algorithm}
{Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996}
{Based on Fig.8.2.21 and Eq.(8.2.40) of Introduction to Signal Processing.
The filtering algorithm is similar to that of lowpass reverb, with the
coefficient choices a = 0, b0 = b1 = 0.5. The length of the delay-line
is designed by tuning it to a desired frequency, i.e., D = fs/f1.
The buffer is filled with D+1 noise samples and the filter is run
with zero input. Sample processing code is the same as in lowpass.dsp.
}
{--- define sampling rate in kHz: ----------------------------------------}
{0xc850 = 8
| 0xc851 = 5.5125
| 0xc852 = 16
}
{0xc853 = 11.025 | 0xc854 = 27.42857 | 0xc855 = 18.9 }
ax0 = 0;
dm(v1) = ax0;
{state v1}
{mr = v0 = sD + a * v1}
{save v0 for later updating v1}
{mr = b0 * v0}
{mr = u = b0 * v0 + b1 * v1}
ay1 = mx1;
ar = mr1 + ay1;
{ay1 = x = input}
{ar = y = x + u = output}
dm(v1) = sr0;
tapin(i2, m2, ar);
cdelay(i2, m2);
EXPERIMENTS
64
dm(tx_buf + 1) = ar;
dm(tx_buf + 2) = ar;
{wrapup}
In this experiment, the sampling rate is set to 44.1 kHz and the generated sound
is the note A440, that is having frequency 440 Hz. The correct amount of delay is
then
D=
44100
fs
=
= 100
f1
440
The delay line must be filled with D+1 random numbers. They were generated
as follows by the routine uran.c given in the Appendix:
uran 0 1 100 2001 | dec2hex 1.15 > w440hz.hex
where the seed value was arbitrary and the output of uran was piped into dec2hex
whose output was the hex file w440hz.hex. Similarly, the file w220hz.hex contains
twice as many random numbers which are used to generate the frequency 220 Hz.
EXPERIMENTS
65
{0xc850 = 8
| 0xc851 = 5.5125
| 0xc852 = 16
}
{0xc853 = 11.025 | 0xc854 = 27.42857 | 0xc855 = 18.9 }
.const fs = 0xc850; {0xc856 = 32
| 0xc857 = 22.05
| 0xc859 = 37.8 }
{0xc85b = 44.1
| 0xc85c = 48
| 0xc85d = 33.075}
{0xc85e = 9.6
| 0xc85f = 6.615
}
{-------------------------------------------------------------------------}
.include <c:\adi_dsp\macros\begin.dsp>;
.const A = 0x7fff;
.const c = 100;
i6 = ^sine; L6 = %sine;
Lab Procedure
a. Go to the directory c:\adi_dsp\examples\guitar. Set D = 100 in the program, copy w440hz.hex into w.hex and run. The program disables the inputs
and simply outputs the re-circulating and gradually decaying random numbers.
b. Repeat by copying w220hz.hex into w.hex and editing the value D = 200 into
the program. The note you hear should be an octave lower.
{mx1 = A * sin(2*pi*f*t)}
{wrapup}
The sinusoidal wavetable has period D = 4000 and is read from the file sinetbl.hex,
which was generated by the DOS commands:
sinetbl 0 1 4000 > sinetbl.dec
dec2hex 1.15 < sinetbl.dec > sinetbl.hex
EXPERIMENTS
66
EXPERIMENTS
67
AM Modulation
{--- return from interrupt -----------------------------------------------}
where
rti;
.include <c:\adi_dsp\macros\end.dsp>;
FM Modulation
The third program, fm.dsp, illustrates FM modulation in which the frequency of a
sinusoid is itself varying sinusoidally. The generated signal is of the form
i5 = ^sine; L5 = %sine;
.const c = 100;
i6 = ^sine; L6 = %sine;
{wrapup}
x(t)= sin 2f (t)t
Because the frequency of the sinusoid is passed into the wavetable generator via
the parameter c, we define the frequency f (t) in the form f (t)= c(t)fs /D, where
c(t)= c0 + Am sin(2fm t)
We choose the modulation depth Am = 0.3c0 , so that c(t) varies sinusoidally
between the limits 0.7c0 c(t) 1.3c0 , and therefore, the frequency will vary in
the limits 0.7f0 f (t) 1.3f0 , where f0 = c0 fs /D. The center frequency is f0 = 200
Hz, corresponding to c0 = 100 and Am = 0.3c0 = 30.
The same wavetable, sinetbl.hex, is used to generate both the parameter c(t)
and the modulated signal x(t). The modulation frequency is fm = cm fs /D, where
cm = 1 resulting in fm = 2 Hz.
{fm.dsp - FM modulation of using common sinusoidal wavetable}
{Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996}
{Based on Sec.8.1.3 of Introduction to Signal Processing}
{--- choose sampling rate in kHz: ----------------------------------------}
{0xc850 = 8
| 0xc851 = 5.5125
| 0xc852 = 16
}
{0xc853 = 11.025 | 0xc854 = 27.42857 | 0xc855 = 18.9 }
.const fs = 0xc850; {0xc856 = 32
| 0xc857 = 22.05
| 0xc859 = 37.8 }
{0xc85b = 44.1
| 0xc85c = 48
| 0xc85d = 33.075}
{0xc85e = 9.6
| 0xc85f = 6.615
}
{-------------------------------------------------------------------------}
.const Am = 30;
.const cm = 1;
i5 = ^sine; L5 = %sine;
EXPERIMENTS
68
.const A = 0x4000;
.const c0 = 100;
i6 = ^sine; L6 = %sine;
{ay1 = Am * sin(2*pi*fm*t)}
{ar = c = c0 + Am * sin(2*pi*fm*t)}
{mx1 = FM-modulated sinusoid}
EXPERIMENTS
e. Run and hear the program fm.dsp with the following three values of the modulation depth: Am = 0.3c0 , Am = c0 , Am = 0.1c0 . Repeat these cases when
the center frequency is changed to f0 = 2000 Hz.
f. Replace the sinusoidal wavetable with the square one, squartbl.hex, and
repeat the case f0 = 200 Hz, Am = 0.3c0 . You will hear a square wave whose
frequency switches between a high and a low value two times a second.
g. Keep the square wavetable that generates the alternating frequency, but generate the signal by a sinusoidal wavetable. To do this , generate a second
sinusoidal wavetable, say of length 800, and define a circular buffer for it
and set one of the DAG registers, e.g., i6, to point to it. Then generate your
FM-modulated sinusoid using this table. The generated signal will be of the
form:
x(t)= sin 2f (t)t ,
69
h. Rewrite the fir3.dsp program of Section 4.3 using calls to the wavetable
generator macro wavgen. Repeat part (c) of the lab procedure of that Section.
rti;
.include <c:\adi_dsp\macros\end.dsp>;
{wrapup}
Lab Procedure
a. Go to directory c:\adi_dsp\examples\wavetabl. Run the program sine.dsp
and listen to the 200 Hz sinusoid. Reset the frequency to 50 Hz, recompile
and run. Keep decreasing the frequency by 10 Hz each time and determine
the lowest frequency you can hear (but, to be fair dont increase the speaker
volume; that would compensate the attenuation introduced by your ears.)
b. Set the frequency at 4000 Hz (the Nyquist frequency.) Recompile and run. Can
you explain what you hear? Replace the sinusoidal wavetable, sinetbl.hex,
with a cosinusoidal one and repeat the experiment at the Nyquist frequency.
c. Replace the sinusoidal table sinetbl.hex with the square wavetable squartbl.hex, which has period 4000 and is equal to +1 for the first half of the
period and 1 for the second half. Run the program with
frequency f = 200
Hz. The wavetable amplitude was chosen to be A = 1/ 2 in order to make the
rms value of the square wave equal to the rms value of the original sinusoid.
d. Run and listen to the program am.dsp, with the initial signal frequency of
f = 200 Hz and envelope frequency of fenv = 2 Hz. Repeat for f = 2000 Hz.
Repeat and explain what you hear for the cases: f = 200 Hz, fenv = 100 Hz.
Then, f = 200 Hz, fenv = 190 Hz. Then, f = 200 Hz, fenv = 200 Hz.
x(n)= s(n)+v(n)
where s(n) is the microphone input and v(n) a periodic interference signal generated internally using a wavetable generator.
Multi-Notch Filter
When the noise is periodic, its energy is concentrated at the harmonics of the fundamental frequency: f1 , 2f1 , 3f1 , and so on. To cancel the entire noise component,
we must use a filter with multiple notches at these harmonics.
As discussed in Section 8.3.2 of the text [1], a simple design arises when the
period of the noise is an integral multiple of the sampling period, that is, T1 = DT,
which implies that the fundamental frequency f1 and its harmonics will be:
f1 =
fs
,
D
fk = kf1 = k
fs
,
D
k = 0, 1, 2, . . .
1 =
k = k1 =
2k
EXPERIMENTS
70
EXPERIMENTS
1z
1 azD
y
sD
s0
1
H(z)=
1 zD
Y(z)
,
=b
X(z)
1 azD
from where we obtain the I/O equation on which the block diagram is based:
Y(z)= bX(z)+zD aY(z)bX(z)
In the experiment, we take the fundamental period to be 800 Hz and the sampling rate 8 kHz. Thus, the period D is:
D=
y = bx + sD
p = s0 = ay bx
cdelay(D, w, & p)
z-D
71
H(z)= b
8000 Hz
fs
= 10
=
f1
800 Hz
The width of the notches is taken to be f = 50 Hz. Then, the design equations
(8.3.27) give the parameter values b = 0.91035, a = 0.8207. Their, 1.15 hex equivalents are 0x7486 and 0x690d, respectively. The magnitude response of this filter
plotted over the right-half of the Nyquist interval is shown below, together with a
magnified view of the notch width at 800 Hz:
}
{--- define sampling rate in kHz: ----------------------------------------}
{0xc850 = 8
| 0xc851 = 5.5125
| 0xc852 = 16
}
{0xc853 = 11.025 | 0xc854 = 27.42857 | 0xc855 = 18.9 }
.const fs = 0xc850; {0xc856 = 32
| 0xc857 = 22.05
| 0xc859 = 37.8 }
{0xc85b = 44.1
| 0xc85c = 48
| 0xc85d = 33.075}
{0xc85e = 9.6
| 0xc85f = 6.615
}
{-------------------------------------------------------------------------}
.include <c:\adi_dsp\macros\begin.dsp>;
.const D = 10;
.var/dm/circ w[D+1];
i2 = ^w; L2 = %w;
.const Ds = 800;
.var/dm/circ s[Ds];
i6 = ^s; L6 = %s;
.const c = 80;
.const A = 0x1000;
.init s: <squartbl.hex>;
{load wavetable}
.const a = 0x690d;
.const b = 0x7486;
EXPERIMENTS
72
my1 = A0;
mr = mr + mx1 * my1 (rnd);
if mv sat mr;
mx1 = mr1;
{save x temporarily}
my0 = b;
mr = mx1 * my0 (rnd);
if mv sat mr;
{mr1 = b * x}
my1 = a;
mr = ar * my1 (ss);
my1 = b;
mr = mr - mx1 * my1 (rnd);
if mv sat mr;
tapin(i2, m2, mr1);
cdelay(i2, m2);
EXPERIMENTS
73
9.7 of the text that the signal v(n) can be expressed in the alternative sinusoidal
form, obtained from the 10-point DFT of one period of the square wave:
v(n)=
1
1 sin 1 n + 2 +
0.4
sin
v(n)= [0, 1, 1, 1, 1, 0, 1, 1, 1, 1, . . . ]
The DOS square wavetable generator squartbl.exe has a command-line option
that allows one to select this type of square wave. Using again the techniques of
Section 9.7, we find for its inverse DFT expansion:
v(n)=
{mr = s0 = a * y - b * x}
sin
Thus, the filter only acts to remove these three odd harmonics. It may appear
puzzling that the Fourier series expansion of this square wave does not contain
exclusively sine terms, as it would in the continuous-time case. This discrepancy
can be traced to the discontinuity of the square wave. In the continuous-time case,
any finite Fourier series sinusoidal approximation to the square wave will vanish at
the discontinuity points. Therefore, a more appropriate discrete-time square wave
might be of the form:
{mr = a * y}
3
3 sin 3 n + 2 + 0.2 cos(5 n)
0.4
0.4
1 sin(1 n)+
tan
0.4
tan
3 sin(3 n)
2
where now only pure sines (as opposed to sines and cosines) appear. The difference
between the above two square waves contains the effect of the discontinuities and
is given by
v(n)= [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, . . . ]
nofilter:
dm(tx_buf + 1) = ar;
dm(tx_buf + 2) = ar;
Lab Procedure
.include <c:\adi_dsp\macros\end.dsp>;
{wrapup}
The filtering operation can be bypassed by uncommenting the jump nofilter instruction, in order to hear the desired signal plus the noise. The particular square
wave of period 10 generated by the program is of the form:
v(n)= [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, . . . ]
It contains only odd harmonics. As discussed in Example 1.4.6 and Section 9.7
of the text, all harmonics that lie outside the Nyquist interval are wrapped inside
the interval and get aliased with the harmonics within the interval. Thus, the above
periodic signal contains only the harmonics 1 = 2/10, 3 = 31 = 6/10, and
5 = 51 = 10/10 = . In fact, we can show using the techniques of Section
a. Go to subdirectory c:\adi_dsp\examples\notch. Generate two square wavetables of the above two types by the DOS commands:
squartbl 1 -1 400 800 1 | dec2hex 1.15 > squartbl.hex
squartbl 1 -1 400 800 0 | dec2hex 1.15 > squartb2.hex
Compile and run the program notch.dsp with the filter off. Speak into the
mike and listen to the interference. Repeat with the filter on. Repeat with the
filter on, but using the second wavetable. Turn off the interference by setting
its amplitude A = 0 and listen to the effect the filter has on your voice input.
EXPERIMENTS
74
b. Estimate the 60-dB time constant (in seconds) of the filter in part (a). Redesign
the notch filter so that its 3-dB width is now f = 2 Hz. What is the new time
constant? Run the new filter and listen to the filter transients as the steadystate gradually takes over and suppresses the noise. Turn off the square wave,
recompile and run. Listen to the impulse response of the filter by lightly
tapping the mike on the table. Can you explain what you are hearing?
c. Generate a square wave with frequency of 1000 Hz, corresponding to wavetable
increment c = 100. Repeat part (a). Now the interference harmonics do not
coincide with the filters notches and you will still hear the interference.
d. Design the correct multi-notch filter that should be used in part (b). Edit the
program notch.dsp to reflect the new design, and run it to verify that it does
indeed remove the 1000 Hz interference.
Single- and Double-Notch Filters
Using a single-notch filter with notch frequency at f1 = 800 Hz instead of the
multi-notch filter would not be sufficient to cancel completely the square wave interference. The third and higher harmonics will survive it. Such a single-notch filter
can be designed with Eqs. (8.2.22) and (8.2.23) of the text, which are implemented
by the MATLAB function parmeq.m. Assuming the same width f = 50 Hz, the
transfer function will be:
H1 (z)=
H3 (z)=
The cascade of the two is a fourth-order filter of the form H13 (z)= H1 (z)H3 (z)
with coefficients obtained by convolving the coefficients of filter-1 and filter-3:
H13 (z)=
0.961852(1 z1 + z2 z3 + z4 )
1 0.980741z1 + 0.961111z2 0.942964z3 + 0.924447z4
The magnitude responses of the two single-notch filters H1 (z), H3 (z) and of
the double-notch filter H13 (z) are shown below:
EXPERIMENTS
75
The program notch1.dsp implements the filter H1 (z) in its direct form using
the macro cdir. The coefficients must be scaled down by a factor of 2 to make
them fit into the 1.15 format. Thus, we must use scaling exponents ea = eb = 1.
{notch1.dsp - single-notch filter at 800 Hz & width 50 Hz - direct form}
{Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996}
{Designed with parmeq.m of Appendix D, with the MATLAB commands:
GB = 1/sqrt(2); f0 = 800; fs = 8000; Df = 50;
[b, a, beta] = parmeq(1, 0, GB, 2*pi*f0/fs, 2*pi*Df/fs);
}
.const M = 2;
.var/dm/circ w[M+1];
.var/dm/circ v[M+1];
.var/pm/circ a[M+1], b[M+1];
{filter order}
{y-delay-line buffer in DM}
{x-delay-line buffer in DM}
{concatenated coeffs in PM}
.const ea = 1;
.const eb = 1;
.const ex = 0;
i2 = ^w; L2 = %w;
i3 = ^v; L3 = %v;
i4 = ^a; L4 = 2*(M+1);
{y-delay-line pointer}
{x-delay-line pointer}
{double-length a,b coefficients}
.const Ds = 800;
.var/dm/circ s[Ds];
i6 = ^s; L6 = %s;
.const c = 80;
.const A = 0x1000;
.init s: <squartbl.hex>;
.init a: <a.hex>;
.init b: <b.hex>;
{load wavetable}
{denominator coefficients}
{numerator coefficients}
EXPERIMENTS
76
my1 = A0;
mr = mr + mx1 * my1 (rnd);
if mv sat mr;
{sr1 = mr1; jump nofilter;}
EXPERIMENTS
77
.include <c:\adi_dsp\macros\begin.dsp>;
.const M = 2;
.var/dm/circ w[M+1];
.var/pm/circ a[M+1], b[M+1];
{filter order}
{circular delay-line buffer in DM}
{concatenated coeffs in PM}
.const ea = 1;
.const eb = 1;
.const ex = 4;
i2 = ^w; L2 = %w;
i4 = ^a; L4 = 2*(M+1);
.const Ds = 800;
.var/dm/circ s[Ds];
i6 = ^s; L6 = %s;
.const c = 80;
.const A = 0x1000;
.init s: <squartbl.hex>;
.init a: <a.hex>;
.init b: <b.hex>;
{load wavetable}
{denominator coefficients}
{numerator coefficients}
{pre-scale x down}
{compute output y}
{input from sr1}
{output in ar}
{post-scale y up}
{wrapup}
input_samples: ena sec_reg;
The program notch2.dsp implements the filter H1 (z) in its canonical form using
the macro ccan. To prevent overflows, the input signal must be scaled down by a
factor of 16 = 24 before it is passed to the filter, and the output scaled up by the
same factor. Thus, the input scaling exponent is ex = 4.
{notch2.dsp - single-notch filter at 800 Hz & width 50 Hz - canonical form}
{Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996}
{Designed with parmeq.m of Appendix D, with the MATLAB commands:
GB = 1/sqrt(2); f0 = 800; fs = 8000; Df = 50;
[b, a, beta] = parmeq(1, 0, GB, 2*pi*f0/fs, 2*pi*Df/fs);
}
{--- define sampling rate in kHz: ----------------------------------------}
{0xc850 = 8
| 0xc851 = 5.5125
| 0xc852 = 16
}
{0xc853 = 11.025 | 0xc854 = 27.42857 | 0xc855 = 18.9 }
.const fs = 0xc850; {0xc856 = 32
| 0xc857 = 22.05
| 0xc859 = 37.8 }
{0xc85b = 44.1
| 0xc85c = 48
| 0xc85d = 33.075}
{0xc85e = 9.6
| 0xc85f = 6.615
}
{-------------------------------------------------------------------------}
my1 = A0;
mr = mr + mx1 * my1 (rnd);
if mv sat mr;
EXPERIMENTS
78
EXPERIMENTS
79
.const c = 80;
.const A = 0x1000;
.init s: <squartbl.hex>;
.init a: <a13.hex>;
.init b: <b13.hex>;
{load wavetable}
{denominator coefficients}
{numerator coefficients}
nofilter:
dm(tx_buf + 1) = sr1;
dm(tx_buf + 2) = sr1;
.include <c:\adi_dsp\macros\end.dsp>;
{wrapup}
The program notch13.dsp implements the filter H13 (z) in its direct form. The
coefficients already fit in 1.15 format and do not need any further scaling.
{notch3.dsp - double-notch filter at 800 Hz and 2400 Hz - canonical form}
{Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996}
{Designed with parmeq.m of Appendix D, with the MATLAB commands:
GB = 1/sqrt(2); f1 = 800; f3 = 2400; fs = 8000; Df = 50;
[b1, a1, beta1] = parmeq(1, 0, GB, 2*pi*f1/fs, 2*pi*Df/fs);
[b3, a3, beta3] = parmeq(1, 0, GB, 2*pi*f3/fs, 2*pi*Df/fs);
a13 = conv(a1, a3); b13 = conv(b1, b3);
}
{--- define sampling rate in kHz: ----------------------------------------}
{0xc850 = 8
| 0xc851 = 5.5125
| 0xc852 = 16
}
{0xc853 = 11.025 | 0xc854 = 27.42857 | 0xc855 = 18.9 }
.const fs = 0xc850; {0xc856 = 32
| 0xc857 = 22.05
| 0xc859 = 37.8 }
{0xc85b = 44.1
| 0xc85c = 48
| 0xc85d = 33.075}
{0xc85e = 9.6
| 0xc85f = 6.615
}
{-------------------------------------------------------------------------}
.include <c:\adi_dsp\macros\begin.dsp>;
my1 = A0;
mr = mr + mx1 * my1 (rnd);
if mv sat mr;
.const A0 = 0x4000;
.const M = 4;
.var/dm/circ w[M+1];
.var/pm/circ a[M+1], b[M+1];
{filter order}
{circular delay-line buffer in DM}
{concatenated coeffs in PM}
.const ea = 0;
.const eb = 0;
.const ex = 3;
.include <c:\adi_dsp\macros\end.dsp>;
i2 = ^w; L2 = %w;
i4 = ^a; L4 = 2*(M+1);
.const Ds = 800;
.var/dm/circ s[Ds];
i6 = ^s; L6 = %s;
rti;
{wrapup}
Lab Procedure
a. Run the program notch1.dsp with the filter off. Then, run it with the filter
on. Do you hear the partial suppression of the interference? Next, replace the
square wavetable with a sinusoidal wavetable generated by the DOS command:
sinetbl 0 1 800 | dec2hex 1.15 > sinetbl.hex
EXPERIMENTS
80
EXPERIMENTS
81
{0xc85e = 9.6
| 0xc85f = 6.615
}
{-------------------------------------------------------------------------}
.include <c:\adi_dsp\macros\begin.dsp>;
{a0 = 0.50}
{a1 = 0.50}
.const D = 400;
.var/dm/circ w[D + 1];
.const D2 = D/2;
i2 = ^w;
L2 = %w;
.const Ds = 4000;
.var/dm/circ sine[Ds];
{sinusoidal wavetable}
{min frequency f1 = fs/Ds = 8/4 = 2 Hz}
i6 = ^sine; L6 = %sine;
Flangers
{--- sample processing algorithm --- process right channel only ----------}
{flanger.dsp - flanging processor}
{Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996}
{Based on Eqs.(8.2.18-8.2.19) of Introduction to Signal Processing.
I/O equation: y(n) = 0.5 * x(n) + 0.5 * x(n-d(n))
Sample processing algorithm:
for each x do:
*p = s0 = x
d = (D - D * sin(2*pi*fc*t)) / 2
variable delay
s1 = tap(D, w, p, d)
y = a0 * s0 + a1 * s1
cdelay(D, w, &p)
}
{--- define sampling rate in kHz: ----------------------------------------}
{0xc850 = 8
| 0xc851 = 5.5125
| 0xc852 = 16
}
{0xc853 = 11.025 | 0xc854 = 27.42857 | 0xc855 = 18.9 }
.const fs = 0xc850; {0xc856 = 32
| 0xc857 = 22.05
| 0xc859 = 37.8 }
{0xc85b = 44.1
| 0xc85c = 48
| 0xc85d = 33.075}
{ay1 = D2 * sin(2*pi*fc*t)}
{ar = d = D2 - D2 * sin(2*pi*fc*t)}
my1 = a0;
mr = mx1 * my1 (ss);
{mr = a0 * x}
mx0 = a1;
mr = mr + mx0 * my0 (rnd);
if mv sat mr;
{a0 * x + a * s1}
EXPERIMENTS
82
EXPERIMENTS
83
.include <c:\adi_dsp\macros\begin.dsp>;
.const ea = 1;
.const eb = 1;
.const ex = 1;
i2 = ^w; L2 = %w;
i3 = ^v; L3 = %v;
i4 = ^a; L4 = 6;
{y-delay-line pointer}
{x-delay-line pointer}
{double-length a,b coefficients}
.const Ds = 4000;
.var/dm/circ s[Ds];
i6 = ^s; L6 = %s;
.const csweep = 5;
{fsweep = 10 Hz}
.const w1 = 0x3244;
.const w2 = 0x1e28;
.const
.const
.const
.const
{b0
{b11
{a2
{a0
rti;
.include <c:\adi_dsp\macros\end.dsp>;
{wrapup}
b0
b11
a2
a0
=
=
=
=
0x3e0c;
0x83e7;
0x3c19;
0x4000;
=
=
=
=
0.9695, scaled by 2}
-2*b0 = -1.9390, scaled by 2}
2*b0-1 = 0.9390, scaled by 2}
1, scaled by 2}
(in Hz)
(in rads/sample)
.init s: <sinetbl.hex>;
.init a: a0, 0, a2;
.init b: b0, 0, b0;
{load wavetable}
{initial denominator coefficients}
{initial numerator coefficients}
}
{--- sample processing algorithm -----------------------------------------}
{--- define sampling rate in kHz: ----------------------------------------}
{0xc850 = 8
| 0xc851 = 5.5125
| 0xc852 = 16
}
{0xc853 = 11.025 | 0xc854 = 27.42857 | 0xc855 = 18.9 }
.const fs = 0xc850; {0xc856 = 32
| 0xc857 = 22.05
| 0xc859 = 37.8 }
{0xc85b = 44.1
| 0xc85c = 48
| 0xc85d = 33.075}
{0xc85e = 9.6
| 0xc85f = 6.615
}
{-------------------------------------------------------------------------}
ax0 = w1;
wavgen(i6, m6, w2, csweep, ay0);
ar = ax0 + ay0;
{ay0 = w2 * sine(sweep*n)}
{ar = w0 = w1 + w2*sin(wsweep*n)}
mr= 0;
mr1 = 0x7fff;
sr = ashift ar by -1 (hi);
{calculate cos(w0)}
{mr = 1}
{sr1 = w0 / 2}
EXPERIMENTS
84
my0 = ar;
mr = mr - sr1 * my0 (rnd);
{my0 = w0}
{mr1 = cos(w0) = 1 - w0^2 / 2}
my0 = b11;
mr = ar * my0 (rnd);
m5 = 1;
modify(i4, m5);
pm(i4, m5) = sr1;
m5 = 2;
modify(i4, m5);
pm(i4, m5) = sr1;
{point to b1}
{save b1, point back to a0}
sr1 = mx1;
{jump nofilter;}
{pre-scale x down}
{compute output y}
cdir(2, i4, m4, i2, m2, i3, m3, ea, eb, sr1);
{input from sr1}
{output in ar}
sr = ashift ar by ex (hi);
{post-scale y up}
sr = ashift sr1 by 1 (hi);
{make it a little louder}
{--- write samples to codec ---------------------------------------------}
EXPERIMENTS
85
As 0 varies, these two filters continuously morph into each other. In the
program, the filter is implemented in its direct form to avoid overflows. Moreover,
because the middle coefficients a1 = b1 = 1.9390 cos 0 take on values in the
2.14 formats range, we have used that format to convert them to hex. This effectively divides the filter coefficients by 2 and must be compensated by using the
scaling exponents ea = eb = 1, corresponding to the scaling factors Ga = 2ea = 2,
Gb = 2eb = 2.
The varying notch frequency is generated using a sinusoidal wavetable. Because
0 remains small, we have calculated cos 0 using the approximation:
cos x = 1
nofilter:
dm(tx_buf + 1) = sr1;
dm(tx_buf + 2) = sr1;
1 2
x
2
Lab Procedure
a. Compile and run the program phaser.dsp. Experiment with lower and higher
values of the sweep frequency.
rti;
{wrapup}
The notch frequency varies sinusoidally between the limits of 200 Hz and 800 Hz:
c. Write another version of the program that calculates cos 0 using the improved approximation:
H(z)=
The magnitude and phase responses of the two filters corresponding to the two
extreme values of the notch frequency f0 = 200, 800 Hz are shown below:
cos x = 1
1 2
1 4
x
x +
2
24
Then, let the notch frequency vary over a somewhat wider range.
EXPERIMENTS
86
.var/dm x[N];
.var/dm y[N];
i2 = ^x;
i3 = ^y;
.init
m2 = 1;
m3 = 1;
EXPERIMENTS
.var/dm x[N];
.var/dm y[N];
.var/dm/circ s[M];
{input samples}
{output samples}
{periodic pulse train}
i2 = ^x;
i3 = ^y;
i5 = ^s;
{input samples}
{output samples}
{pulse train}
.init
x: <x.hex>;
m2 = 1; L2 = 0;
m3 = 1; L3 = 0;
L5 = %s; m5 = 1;
x: <x.hex>;
jump
rti;
rti;
rti;
rti;
rti;
rti;
{input samples}
{output samples}
L2 = 0;
L3 = 0;
87
{DSP macros}
.module/ram/abs=0 decim1;
.const B = 8;
.const L = 16 - B;
.const N = 8;
{input samples}
{output samples}
jump
rti;
rti;
rti;
rti;
rti;
rti;
start:
cntr=N;
do output until ce;
mr1 = dm(i2, m2);
sr = ashift mr1 by -L (hi);
sr = ashift sr1 by L (hi);
output: dm(i3, m3) = sr1;
cntr=M;
do zero_s until ce;
zero_s:
dm(i5, m5) = 0;
{initialize s to zero}
ax1 = A;
dm(s) = ax1;
{set s[0] = A}
{s = [A,0,...,0] = A and M-1 zeros}
cntr=N;
do output until ce;
mr1 = dm(i2, m2);
ar = dm(i5, m5);
ar = pass ar;
if ne jump output;
{else}
{if AR = 0, output is 0}
mr1 = 0;
idle;
output: dm(i3, m3) = mr1;
.endmod;
idle;
Downsampling Example
.endmod;
{DSP macros}
Delay Examples
The next two simulator examples illustrate linear and circular delay-line buffers
and are based on Example 4.2.1 of the text [1]. The input and 3-fold delayed output
signals are:
x=
1
[1, 1, 2, 1, 2, 2, 1, 1]
4
y=
1
[0, 0, 0, 1, 1, 2, 1, 2, 2, 1, 1]
4
.module/ram/abs=0 decim1;
.const M = 3;
.const N = 8;
.const A = 0x7fff;
{decimation ratio}
{number of input samples}
{sampling pulse amplitude, A=1}
They have been scaled by 4 to fit into the 1.15 format. The corresponding 1.15 hex
input samples are:
EXPERIMENTS
88
EXPERIMENTS
89
my0 = dm(w+1);
dm(w+2) = my0;
{w[1] = tap-1}
{w[2] = w[1]}
my0 = dm(w);
dm(w+1) = my0;
{w[0] = tap-0}
{w[1] = w[0]}
Linear Buffer
The program delex1.dsp implements the 3-fold delay using a linear buffer:
{delex1.dsp - delay example using linear buffer}
{Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996}
transients:
{DSP macros}
idle;
.endmod;
delex1;
Circular Buffer
.var/dm
.var/dm
.var/dm
.init
.init
x[8];
y[11];
w[4];
{input samples}
{output samples}
{linear delay-line buffer in DM}
The program delex2.dsp implements the 3-fold delay using a circular buffer:
{delex2.dsp - delay example using circular buffer}
{Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996}
x: <xfir.hex>;
w: 0x0000, 0x0000, 0x0000, 0x0000;
i3 = ^x;
i5 = ^y;
m3 = 1;
m5 = 1;
L3 = 0;
L5 = 0;
cntr = 8;
do outputs until ce;
my1 = dm(w+3);
dm(i5, m5) = my1;
mx1 = dm(i3, m3);
dm(w) = mx1;
outputs:
.module/ram/abs=0
{input samples}
{output samples}
my0 = dm(w+1);
dm(w+2) = my0;
{w[1] = tap-1}
{w[2] = w[1]}
my0 = dm(w);
dm(w+1) = my0;
{w[0] = tap-0}
{w[1] = w[0]}
{three input-off transients}
{get tap-3}
{save in y and point to next y}
start:
{input samples}
{output samples}
{circular delay-line buffer in DM}
x: <xfir.hex>;
w: 0x0000, 0x0000, 0x0000, 0x0000;
jump
rti;
rti;
rti;
rti;
rti;
rti;
my0 = dm(w+2);
dm(w+3) = my0;
mx0 = 0;
dm(w) = mx0;
.init
.init
{DSP macros}
delex1;
.var/dm
x[8];
.var/dm
y[11];
.var/dm/circ w[4];
cntr = 3;
do transients until ce;
my1 = dm(w+3);
dm(i5, m5) = my1;
.include <c:\adi_dsp\macros\dspmac.dsp>;
i2 = ^w;
i3 = ^x;
i5 = ^y;
L2 = %w;
m3 = 1; L3 = 0;
m5 = 1; L5 = 0;
cntr = 8;
do outputs until ce;
m2 = 3; modify(i2, m2);
m2 = 0; my1 = dm(i2, m2);
m2 =-3; modify(i2, m2);
{circular buffer}
{input samples}
{output samples}
{write y}
{get input x}
{put it into tap-0}
EXPERIMENTS
outputs:
90
m2 = -1;
modify(i2, m2);
EXPERIMENTS
91
h = 0.25
0.50
-0.25
0.25
cntr = 3;
do transients until ce;
m2 = 3; modify(i2, m2);
m2 = 0; my1 = dm(i2, m2);
m2 =-3; modify(i2, m2);
transients:
{3 input-off transients}
= 1/4
= 2/4
= -1/4
= 1/4
x = 0.25
0.25
0.50
0.25
0.50
0.50
0.25
0.25
{point to tap-3}
{get tap-3}
{point back to tap-0}
{write y}
mx1 = 0;
m2 = 0; dm(i2, m2) = mx1;
{zero input x}
{put it into tap-0}
m2 = -1;
modify(i2, m2);
=
=
=
=
=
=
=
=
1/4
1/4
2/4
1/4
2/4
2/4
1/4
1/4
y = 0.0626
0.1875
0.1875
0.3125
0.1875
0.4375
0.2500
0.1875
0.1875
0.0000
0.0625
=
=
=
=
=
=
=
=
=
=
=
1/16
3/16
3/16
5/16
3/16
7/16
4/16
3/16
3/16
0/16
1/16
(The h-coefficients and x have been scaled down to fit in 1.15 format,
as compared to Example 4.2.1.)
}
.include <c:\adi_dsp\macros\dspmac.dsp>;
{DSP macros}
idle;
.module/ram/abs=0
firex1;
.endmod;
1
[1, 2, 1, 1],
4
x=
1
[1, 1, 2, 1, 2, 2, 1, 1]
4
.const
.const
.var/dm/ram
.var/dm/ram
.var/dm/ram/circ
.var/pm/ram/circ
.init
.init
1
[1, 3, 3, 5, 3, 7, 4, 3, 3, 0, 1]
16
0xe000, 0x2000]
0x4000, 0x2000, 0x4000, 0x4000, 0x2000, 0x2000]
0x1800, 0x2800, 0x1800, 0x3800, 0x2000, 0x1800,
0x0800]
The filter and input hex numbers can also be obtained by converting the unscaled
signals in the 3.13 format. The output samples can be converted using hex2dec
into their unscaled decimal versions using the 5.11 format to compensate for the
factor 16.
The program firex1.dsp implements the FIR filter using the macros tapin,
dot, and cdelay:
{firex1.dsp - FIR example using tapin, dot, cdelay}
{Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996}
{Based on Example 4.2.1 of Introduction to Signal Processing. The filter
coefficients and are stored in the files h.dec/h.hex. The input and
output signals are in the files xfir.dec/xfir.hex, yfir.dec/yfir.hex.
start:
L=8;
M=3;
x[L];
y[L+M];
w[M+1];
h[M+1];
x: <xfir.hex>;
h: <h.hex>;
jump
rti;
rti;
rti;
rti;
rti;
rti;
i2
i4
i3
i5
^w;
^h;
^x;
^y;
=
=
=
=
m2
m4
m3
m5
=
=
=
=
0;
0;
1;
1;
L2
L4
L3
L5
=
=
=
=
%w;
%h;
0;
0;
EXPERIMENTS
92
cdelay(i2, m2);
dm(i5, m5) = mr1;
transients:
{update delay}
{output y}
idle;
.endmod;
The program firex2.dsp implements the FIR filter using the macro cfir:
{firex2.dsp - FIR example using cfir}
{Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996}
{Based on Example 4.2.1 of Introduction to Signal Processing. The filter
coefficients and are stored in the files h.dec/h.hex. The input and
output signals are in the files xfir.dec/xfir.hex, yfir.dec/yfir.hex.
h = 0.25
0.50
-0.25
0.25
= 1/4
= 2/4
= -1/4
= 1/4
x = 0.25
0.25
0.50
0.25
0.50
0.50
0.25
0.25
=
=
=
=
=
=
=
=
1/4
1/4
2/4
1/4
2/4
2/4
1/4
1/4
y = 0.0626
0.1875
0.1875
0.3125
0.1875
0.4375
0.2500
0.1875
0.1875
0.0000
0.0625
=
=
=
=
=
=
=
=
=
=
=
1/16
3/16
3/16
5/16
3/16
7/16
4/16
3/16
3/16
0/16
1/16
(The h-coefficients and x have been scaled down to fit in 1.15 format,
as compared to Example 4.2.1.)
}
.include <c:\adi_dsp\macros\dspmac.dsp>;
.module/ram/abs=0
.const
.const
.var/dm/ram
.var/dm/ram
.var/dm/ram/circ
.var/pm/ram/circ
.init
.init
start:
cntr = L;
do outputs until ce;
ax0 = dm(i3, m3);
cfir(M, i4, m4, i2, m2, ax0);
outputs:
dm(i5, m5) = mr1;
cntr = M;
do transients until ce;
ax0 = 0;
cfir(M, i4, m4, i2, m2, ax0);
transients:
dm(i5, m5) = mr1;
idle;
.endmod;
H(z)=
where the b-coefficients have been scaled down by 4 to fit into the 1.15 format.
Similarly, the input has been scaled down by 8:
x=
1
[1, 3, 2, 5, 4, 6, 0, 0, 0]
8
1
[1, 4, 7, 14, 17, 27, 28, 29, 27]
32
The program canex1.dsp implements an IIR filter using the canonical realization
macro ccan:
i2
i4
i3
i5
^w;
^h;
^x;
^y;
L3 = 0;
L5 = 0;
y=
jump
rti;
rti;
rti;
rti;
rti;
rti;
=
=
=
=
93
The expected output from that example was worked out in the text:
L=8;
M=3;
x[L];
y[L+M];
w[M+1];
h[M+1];
L2
L4
m3
m5
EXPERIMENTS
{dsp macros}
firex2;
x: <xfir.hex>;
h: <h.hex>;
=
=
=
=
a = [1, 0, 0, -1]
b = [1, 1, 2, 0] / 4 = [0.25, 0.25, 0.50, 0]
The coefficients define a double-length concatenated circular buffer i7,
and are stored in the files a.dec/a.hex and b.dec/b.hex. The input and
output signals are in the files xiir.dec/xiir.hex, yiir.dec/yiir.hex:
x = 0.125 = 0x1000 = 1/8
0.375 = 0x3000 = 3/8
y = 0.03125 = 0x0400 =
0.12500 = 0x1000 =
1/32
4/32
EXPERIMENTS
94
0.250
0.625
0.500
0.750
0.000
0.000
0.000
=
=
=
=
=
=
=
0x2000
0x5000
0x4000
0x6000
0x0000
0x0000
0x0000
=
=
=
=
=
=
=
2/8
5/8
4/8
6/8
0/8
0/8
0/8
0.21875
0.43750
0.53125
0.84375
0.87500
0.90625
0.84375
=
=
=
=
=
=
=
0x1c00
0x3800
0x4400
0x6c00
0x7000
0x7400
0x6c00
=
=
=
=
=
=
=
7/32
14/32
17/32
27/32
28/32
29/32
27/32
EXPERIMENTS
95
i2
i4
i5
i7
=
=
=
=
^w;
^x;
^y;
^a;
L2 = %w;
m4 = 1; L4 = 0;
m5 = 1; L5 = 0;
L7 = 2*(M+1);
{delay-line buffer}
{input samples}
{output samples}
{double-length a,b coef-}
{-ficient buffer}
{clear delay line}
cntr = L;
do outputs until ce;
sr1 = dm(i4, m4);
sr = ashift sr1 by -ex (hi);
The a,b coefficients that are used in the program and read from
the file ab.hex are the related to the true a,b coefficients by
the following scale factors whoes exponents are passed to ccan():
a_used = 2^(-ea) * a_true
b_used = 2^(-eb) * b_true
outputs:
{read input x}
{pre-scale x down}
{compute output y}
{input from sr1}
{output in sr1}
{post-scale y up}
{write y}
idle;
In this example: ea = 0, eb = 0, (no further scaling is needed)
.endmod;
The input samples x must also, in general, be scaled down before
passed into ccan() and then the output from ccan() must be scaled up:
x1 = 2^(-ex) * x
y1 = ccan(x1)
y = 2^ex * y1
input to ccan()
output from ccan() due to x1
output due to x
Further down-scaling of the input and up-scaling of the output are necessary to
avoid overflows. The input scaling exponent was ex = 1. The program direx1.dsp
implements an IIR filter using the direct-form realization macro cdir. It does not
need any input down-scaling and therefore ex = 0.
{direx1.dsp - direct-form-I realization example using cdir}
{Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996}
}
.include <c:\adi_dsp\macros\dspmac.dsp>;
.module/ram/abs=0
.const
.const
.var/dm
.var/dm
.var/dm/circ
.var/pm/circ
.init
.init
.init
canex1;
L=9;
M=3;
x[L];
y[L];
w[M+1];
a[M+1], b[M+1];
x: <xiir.hex>;
a: <a.hex>;
b: <b.hex>;
.const ea = 0;
.const eb = 0;
.const ex = 1;
jump
rti;
rti;
rti;
rti;
rti;
{dsp macros}
=
=
=
=
=
=
=
=
=
0x1000
0x3000
0x2000
0x5000
0x4000
0x6000
0x0000
0x0000
0x0000
=
=
=
=
=
=
=
=
=
1/8
3/8
2/8
5/8
4/8
6/8
0/8
0/8
0/8
y = 0.03125
0.12500
0.21875
0.43750
0.53125
0.84375
0.87500
0.90625
0.84375
=
=
=
=
=
=
=
=
=
0x0400
0x1000
0x1c00
0x3800
0x4400
0x6c00
0x7000
0x7400
0x6c00
=
=
=
=
=
=
=
=
=
1/32
4/32
7/32
14/32
17/32
27/32
28/32
29/32
27/32
(The b-coefficients and x have been scaled down to fit in 1.15 format,
as compared to Example 7.5.4.)
Scaling factors:
The a,b coefficients that are used in the program and read from
EXPERIMENTS
96
MACROS
97
the file ab.hex are the related to the true a,b coefficients by
the following scale factors whoes exponents are passed to cdir():
{pre-scale x down}
{compute output y}
cdir(M, i7, m7, i2, m2, i3, m3, ea, eb, sr1);
{input in sr1}
{output in ar}
sr = ashift ar by ex (hi);
{post-scale y up}
dm(i5, m5) = sr1;
{write y}
idle;
The input samples x must also, in general, be scaled down before
passed into cdir() and then the output from cdir() must be scaled up:
x1 = 2^(-ex) * x
y1 = cdir(x1)
y = 2^ex * y1
input to cdir()
output from cdir() due to x1
output due to x
.endmod;
Lab Procedure
Go to directory c:\adi_dsp\examples\sim. Compile and load each program into
the simulator by running the batch file ezs.bat, that is,
}
.include <c:\adi_dsp\macros\dspmac.dsp>;
.module/ram/abs=0
.const
.const
.var/dm
.var/dm
.var/dm/circ
.var/dm/circ
.var/pm/circ
.init
.init
.init
direx1;
L=9;
M=3;
x[L];
y[L];
w[M+1];
v[M+1];
a[M+1], b[M+1];
x: <xiir.hex>;
a: <a.hex>;
b: <b.hex>;
.const ea = 0;
.const eb = 0;
.const ex = 0;
start:
{dsp macros}
jump
rti;
rti;
rti;
rti;
rti;
rti;
i2
i3
i4
i5
i7
^w;
^v;
^x;
^y;
^a;
{y-delay-line buffer}
{x-delay-line buffer}
{input samples}
{output samples}
{double-length a,b coef-}
{-ficient buffer}
{clear y-delay line}
{clear x-delay line}
=
=
=
=
=
L2
L3
m4
m5
=
=
=
=
%w;
%v;
1; L4 = 0;
1; L5 = 0;
L7 = 2*(M+1);
ezs
ezs
ezs
ezs
ezs
ezs
ezs
ezs
quantex1
dnsamp1
delex1
delex2
firex1
firex2
canex1
direx1
Three predefined data windows will open. Using the <CTRL-G> command, set the
windows to display the contents of the arrays y, x, and w. The windows can be
toggled from hex to decimal format using <CTRL-T>.
Step through the programs by issuing the command step in the command line
window. Observe and write down the contents of the delay-line buffer w, and the
input and output arrays x, y, as each instruction is executed and as each input
sample gets processed.
5. Macros
In this section we give the source code of the following macros used in the experiments:
{read input x}
zero.dsp
tap.dsp
tapin.dsp
cdelay.dsp
dot.dsp
cfir.dsp
ccan.dsp
cdir.dsp
wavgen.dsp
MACROS
98
MACROS
99
5.1. zero
.macro tap(%0, %1, %2, %3);
%1 = %2;
%1 = -%2;
modify(%0, %1);
%3 = dm(%0, %1);
.endmacro;
%0 = pointer to delay-line buffer, e.g., I2
%1 = M-register to use with buffer, e.g., M2
%2 = length of buffer, e.g., L2
typical usage:
-------------zero(i2, m2, L2);
internal operation:
------------------cntr = L2; m2 = 1;
do loop until ce;
loop:
dm(i2, m2) = 0;
5.3. tapin
The macro tapin transfers the content of a data register into tap-0 of a delay line.
The analogous statement in the text is p = x, which puts x into the buffer location
pointed to by the pointer p. Here, the role of the pointer is played by the I-register:
{tapin.dsp - put input sample into tap-0 of delay line.
Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996.
%0 = pointer to delay-line buffer, e.g., I2
%1 = M-register to use with buffer, e.g., M2
%2 = data register holding input, e.g.,
ax0, ax1, ay0, ay1, ar, mx0, mx1, my0, my1, mr1, sr0, sr1
}
.macro zero(%0, %1, %2);
.local loop;
typical usage:
-------------tapin(i2, m2, mx1);
cntr = %2; %1 = 1;
do loop until ce;
loop:
dm(%0, %1) = 0;
internal operation:
------------------m2 = 0;
dm(i2, m2) = mx1;
.endmacro;
}
5.2. tap
The macro tap allows the accessing of the tap outputs of a delay line and is modeled
after the routines tap.c and tap2.c of the text [1]:
{tap.dsp - tap outputs of circular delay line.
Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996.
Based on tap.c and tap2.c of Introduction to Signal Processing.
%0
%1
%2
%3
=
=
=
=
typical usage:
-------------tap(i2, m2, d, sr1);
internal operation:
------------------m2 = d;
modify(i2, m2);
m2 =-d;
sr1 = dm(i2, m2);
}
%1 = 0;
.endmacro;
5.4. cdelay
The macro cdelay is the assembly code equivalent of the routines cdelay.c and
cdelay2.c of the text and its effect is to decrement the circular pointer pointing
into the delay-line buffer:
{cdelay.dsp - update circular delay-line buffer.
Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996.
typical usage:
-------------cdelay(i2, m2);
MACROS
100
MACROS
101
if mv sat mr;
internal operation:
------------------m2 = -1; modify(i2, m2);
.endmacro;
(i.e., backshift pointer i2)
}
.macro cdelay(%0, %1);
%1 = -1;
modify(%0, %1);
{backshift pointer}
When dot is used in the implementation of IIR filters, the DAG pointer i4 points
to a double-length circular buffer in PM consisting of the concatenation of the denominator and numerator coefficients. In that case, it takes two calls of dot for i4
to wrap around completely. See the macros ccan and cdir.
.endmacro;
5.6. cfir
5.5. dot
The macro dot employs multifunction instructions to compute the dot product of
two circular buffers, one residing in DM and the other in PM. It is modeled after the
FIR filtering routine fir.dsp found in [3].
The dot-product result is returned in mr1. The I-register circular pointers do not
changethey cycle completely around to their entry values:
The macro cfir is the assembly code equivalent of the routines cfir.c and cfir2.c
of the text. It takes its input from a data register and puts it in tap-0 of the delay
line, then it computes the dot-product output of the filter and returns it into mr1,
and finally it updates the delay line:
{cfir.dsp - direct-form FIR filter of order M using circular buffers.
Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996.
Based on cfir.c and cfir2.c of Introduction to Signal Processing.
In book: y = cfir(M, h, w, &p, x);
=
=
=
=
=
%0
%1
%2
%3
%4
%5
loop:
typical usage:
-------------cfir(M, i4, m4, i2, m2, mx1);
internal operation:
------------------tapin(i2, m2, mx1);
dot(M, i4, m4, i2, m2);
cdelay(i2, m2);
}
.macro cfir(%0, %1, %2, %3, %4, %5);
}
.macro dot(%0, %1, %2, %3, %4);
.local loop;
loop:
typical usage:
-------------dot(M, i4, m4, i2, m2);
internal operation:
------------------m2 = 1; m4 = 1;
mr = 0, mx0 = dm(i2, m2), my0 = pm(i4, m4);
cntr = M;
do loop until ce
mr = mr + mx0 * my0 (ss), mx0 = dm(i2, m2), my0 = pm(i4, m4);
mr = mr + mx0 * my0 (rnd);
if mv sat mr;
=
=
=
=
=
=
%2 = 1; %4 = 1;
mr = 0, mx0 = dm(%3, %4), my0 = pm(%1, %2);
cntr = %0;
do loop until ce;
mr = mr + mx0 * my0 (ss), mx0 = dm(%3, %4), my0 = pm(%1, %2);
mr = mr + mx0 * my0 (rnd);
MACROS
102
MACROS
5.7. ccan
------------------mx1 = ax1;
mr1 = 0;
tapin(i2, m2, mr1);
dot(M, i4, m4, i2, m2);
sr = ashift mr1 by ea (hi);
mr = 0;
mr1 = sr1;
my0 = 0x8000;
mr = mr + mx1 * my0 (rnd);
if mv sat mr;
ar = -mr1;
tapin(i2, m2, ar);
dot(M, i4, m4, i2, m2);
sr = ashift mr1 by eb (hi);
cdelay(i2, m2);
The macro ccan implements the canonical realization of an IIR filter and is the
assembly code equivalent of the routines ccan.c and can2.c of the text. It takes
its input from a data register, returns its output into sr1, and updates the delay
line:
{ccan.dsp - canonical-form IIR filter of order M using circular buffers.
Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996.
Based on ccan.c and can2.c of Introduction to Signal Processing.
%0
%1
%2
%3
%4
%5
%6
%7
=
=
=
=
=
=
=
=
filter order M
pointer to filter taps buffer in PM, e.g., i4
m-register to use with tap buffer,
e.g., m4
pointer to delay-line buffer in DM, e.g., i2
m-register to use with delay buffer, e.g., m2
exponent for a-coefficient scale factor
exponent for b-coefficient scale factor
data register holding input, e.g.,
ax0, ax1, ay0, ay1, ar, mx0, mx1, my0, my1, mr1, sr0, sr1
103
}
.macro ccan(%0, %1, %2, %3, %4, %5, %6, %7);
mx1 = %7;
mr1 = 0;
tapin(%3, %4, mr1);
dot(%0, %1, %2, %3, %4);
sr = ashift mr1 by %5 (hi);
mr = 0;
mr1 = sr1;
my0 = 0x8000;
mr = mr + mx1 * my0 (rnd);
if mv sat mr;
ar = -mr1;
tapin(%3, %4, ar);
dot(%0, %1, %2, %3, %4);
sr = ashift mr1 by %6 (hi);
cdelay(%3, %4);
{mx1 = input x}
{put s0 = 0 into tap-0}
{mr1 = dot(M,a,s)}
{sr1 = Ga * dot(M,a,s)}
{mr = Ga * dot(M,a,s)}
{facilitates MAC usage}
{mr = Ga * dot(M,a,s) - x}
{ar = s0 = x - Ga * dot(M,a,s)}
{input s0 into tap-0}
{mr1 = dot(M,b,s)}
{sr1 = y = Gb * dot(M,b,s)}
.endmacro;
Fig. 5.1 shows this realization together with the necessary scale factors. To fit within
the 1.15 format, the filter coefficients must be scaled by appropriate factors of the
form:
Gb = 2eb
Ga = 2ea ,
Ga
1
Gb
[1, a1 , . . . , aM ]=
[b0 , b1 , . . . , bM ]=
Ga
1
Gb
a
b
where a are the scaled coefficients that will be loaded in DM, and a are the true
coefficients. (Note that a0 is no longer unity, but that does not matter because it
is not really used in the computation.) The overall transfer function is not affected
by these scale factors; only the intermediate calculations.
MACROS
104
1/Gx
1/Ga
Ga
b0
s0
-1
a1 s z
1
a2
z-1
s2
Gb
Gx
b1
b2
To be able to use the dot-product macro dot, we write the sample processing
algorithm for the true coefficients in the form of Eq. (7.2.6) of the text, which we
have modified here to use a circular delay-line buffer w with state vector s defined
relative to the buffer pointer p:
for each input sample x do:
p = s0 = 0
p = s0 = x dot(M, a, s)
y = dot(M, b, s)
cdelay(M, w, & p)
Writing the unscaled coefficients in terms of the scaled ones, a = Ga a and
b = Gb b , we obtain the sample processing algorithm depicted in the above block
diagram:
for each input sample x do:
p = s0 = 0
p = s0 = x Ga dot(M, a , s)
y = Gb dot(M, b , s)
cdelay(M, w, & p)
The canonical realization is more prone to overflows than the direct or transposed forms. In such cases the input x must also be scaled down by an appropriate
scale factor of the form Gx = 2ex . After filtering, the output may be scaled back
up by the same factor as shown in the block diagram. The input scaling is not implemented in the above sample processing algorithm. If necessary, it may be done
outside ccan using the shifter.
In the main program, the denominator and numerator coefficients a and b must
be concatenated into a double-length circular buffer. If the DAG pointer i4 is set
to point to the beginning of a and i2 to the state vector s, then after the first call of
dot, i2 will wrap around completely, and i4 will be left pointing to the beginning of
b. Only after the second call of dot will the pointer i4 wrap around to the beginning
of a, while i2 will wrap around again. Thus, upon exit from ccan, i4 will remain
unchanged, whereas i2 will be backshifted due to the last call of cdelay.
MACROS
105
5.8. cdir
The macro cdir implements the direct-form-I realization and is the assembly code
equivalent of the routines dir.c and dir2.c of the text. It takes its input from a
data register, returns its output into ar, and updates the delay lines:
{cdir.dsp - direct-form-I IIR filter of order M using circular buffers.
Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996.
Based on dir.c and dir2.c of Introduction to Signal Processing.
%0
%1
%2
%3
%4
%5
%6
%7
%8
%9
=
=
=
=
=
=
=
=
=
=
filter order M
pointer to filter taps buffer in PM, e.g., i4
m-register to use with tap buffer,
e.g., m4
pointer to y-delay-line buffer in DM, e.g., i2
m-register to use with y-delay buffer, e.g., m2
pointer to x-delay-line buffer in DM, e.g., i3
m-register to use with x-delay buffer, e.g., m3
exponent for a-coefficient scale factor
exponent for b-coefficient scale factor
data register holding input, e.g.,
ax0, ax1, ay0, ay1, ar, mx0, mx1, my0, my1, mr1, sr0, sr1
MACROS
106
internal operation:
------------------tapin(i3, m3, ax1);
mr = 0;
tapin(i2, m2, mr1);
dot(M, i4, m4, i2, m2);
sr = ashift mr1 by ea (hi);
ay1 = sr1;
dot(M, i4, m4, i3, m3);
sr = ashift mr1 by eb (hi);
ar = sr1 - ay1;
tapin(i2, m2, ar);
cdelay(i2, m2);
cdelay(i3, m3);
MACROS
107
pv = u0 = x
pw = s0 = 0
pw = y = s0 = Gb dot(M, b , u)Ga dot(M, a , s)
cdelay(M, v, & pv )
cdelay(M, w, & pw )
where pv and pw are the x-delay and y-delay buffer pointers, v, w are the corresponding buffers, and u, s the corresponding states relative to the current values
of the pointers.
5.9. wavgen
.macro cdir(%0, %1, %2, %3, %4, %5, %6, %7, %8, %9);
tapin(%5, %6, %9);
mr = 0;
tapin(%3, %4, mr1);
dot(%0, %1, %2, %3, %4);
sr = ashift mr1 by %7 (hi);
ay1 = sr1;
dot(%0, %1, %2, %5, %6);
sr = ashift mr1 by %8 (hi);
ar = sr1 - ay1;
tapin(%3, %4, ar);
cdelay(%3, %4);
cdelay(%5, %6);
{u0 = x = input}
{s0 = 0}
{dot(M,a,s)}
{Ga * dot(M,a,s)}
{dot(M,b,u)}
{Gb * dot(M,b,u)}
{output y in AR}
{s0 = y}
{update y-delay}
{update x-delay}
f =c
.endmacro;
The coefficients must be scaled in the same way as in the canonical form. Fig. 5.2
shows the scaled realization.
b0
1/Gx
u0
-1
u1
z-1
u2
b1
b2
Gb /Ga
fs
D
Ga
Gx
-1
a1
a2
z-1
s0
s1
s2
%0
%1
%2
%3
%4
=
=
=
=
=
The sample processing algorithm is the circular version of that in Eq. (7.1.10)
of the text and can be stated in the following form that uses the scaled coefficients
(again, the input prescaling is not implemented inside cdir):
typical usage:
-------------wavgen(i5, m5, A, c, my1);
internal operation:
------------------m5 = c;
mx0 = A;
my0 = dm(i5, m5);
mr = mx0 * my0 (rnd);
APPENDIX
108
my1 = mr1;
6. Appendix
This appendix contains the listings of the following files, all of which reside in the
directory c:\adi_dsp\macros:
dec2hex.c, hex2dec.c
sinetbl.c, squartbl.c, trapztbl.c, uran.c
template.dsp, dspmac.dsp
void adc();
APPENDIX
109
#include
#include
#include
#include
a = atoi(argv[1]);
B = a + atoi(strchr(argv[1], .) + 1);
total bits
R = pow(2, a);
B-bit vector
B/4 hex digits
convert to binary
+ 2 * b[4*i+2]
The program hex2dec.c performs the converse operation, that is, hex-to-decimal
conversion. For each hex number to be converted, it reads its hex digits as chars,
then works out the binary bit pattern of the number, and then calls the text routine
dac.c to convert it to decimal. Again, one must have a + b = B, where the program
assumes that there will be B/4 hex chars in each hex number to be converted:
/* hex2dec.c -- hex to decimal (a.b)-format converter
* Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996
*
* Usage: hex2dec a.b < hex.dat > decimal.dat
*
*/
#include
#include
#include
#include
#include
<stdio.h>
<math.h>
<stdlib.h>
<ctype.h>
<string.h>
if (argc != 2) {
puts("\nDecimal to hex (a.b)-format converter");
puts("Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996\n");
puts("Usage: dec2hex a.b < decimal.dat > hex.dat");
puts("e.g., dec2hex 1.15 < decimal.dat > hex.dat \n");
+ b[4*i+3];
double dac();
int c2h();
void main(int argc, char ** argv)
{
char *s;
APPENDIX
110
APPENDIX
111
int i, j, a, B, *b;
double x, R;
if (argc != 2) {
puts("\nHex to decimal (a.b)-format converter");
puts("Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996\n");
puts("Usage: hex2dec a.b < hex.dat > decimal.dat");
puts("e.g., hex2dec 1.15 < hex.dat > decimal.dat\n");
puts("Total bits: B = a+b, (must be a multiple of 4)");
puts("Full scale range is R = 2^a");
puts("Inputs must have B/4 hex digits\n");
puts("For the (a.b)-format, the outputs are in the range:");
puts("
-2^(a-1) <= x <= 2^(a-1) - 2^(-b)");
exit(0);
}
a = atoi(argv[1]);
B = a + atoi(strchr(argv[1], .) + 1);
total bits
B-bit vector
B/4 hex digits
R = pow(2, a);
hex chars
i-th hex digit
j-th bit
masks: 8,4,2,1
scaled output
end of main
The program squartbl.c generates one period of length D of a square wave. One
can choose between (a) a completely discontinuous jumping between two levels, or
(b) a more gradual transition where the values at the discontinuities are replaced
by the mid value between the levels.
/* squartbl.c - square wavetable of period D and subperiod D1 */
/* Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996 */
#include <stdio.h>
#include <math.h>
APPENDIX
puts("
puts("
puts("
puts("
puts("
exit(0);
}
112
APPENDIX
113
if (i < D1)
printf("%.15lf\n", i * A / D1);
else
if (i < D1 + D2)
printf("%.15lf\n", A);
else
printf("%.15lf\n", (D - i) * A / (D - D1 - D2));
}
A1
A2
D1
D
x
=
=
=
=
=
atof(argv[1]);
atof(argv[2]);
atoi(argv[3]);
atoi(argv[4]);
1 - atof(argv[5]);
x=1-t
A1 or (A1+A2)/2
A2 or (A1+A2)/2
#include <stdio.h>
#include <math.h>
if (argc != 5) {
puts("Generate a block uniform random numbers");
puts("Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996\n");
puts("Usage: uran m R N iseed > x.dat\n");
puts("N = length of block");
puts("range is the interval [m-R, m+R), mean = m\n");
exit(0);
}
m = atof(argv[1]);
R = atof(argv[2]);
N = atoi(argv[3]);
iseed = atol(argv[4]);
for (i=0; i<N; i++) {
x = 2 * R * (ran(&iseed) - 0.5) + m;
printf("% .15lf\n", x);
}
}
A =
D1 =
D2 =
D =
atof(argv[1]);
atof(argv[2]);
atoi(argv[3]);
atoi(argv[4]);
APPENDIX
114
115
We have made only two modifications to these files: (1) we redefined the codecs
data format register so that it can be passed in as the constant fs, which may be
selected at the beginning of every example program, and (2) we added the includefile dspmac.dsp, which includes all the DSP macros; its listing is:
{dspmac.dsp - includes all DSP macros.
Junior DSP Lab - Rutgers ECE Dept - S. J. Orfanidis - Jan 1996.
typical usage:
cdelay(i2, m2);
tap(i2, m2, d, my1);
tapin(i2, m2, mx1);
dot(M, i4, m4, i2, m2);
cfir(M, i4, m4, i2, m2, mx1);
ccan(M, i4, m4, i2, m2, ea, eb, ax1);
cdir(M, i4, m4, i2, m2, i3, m3, ea, eb, ax1);
wavgen(i5, m5, A, c, my1);
zero(i2, m2, L2);
REFERENCES
}
.include
.include
.include
.include
.include
.include
.include
.include
.include
<c:\adi_dsp\macros\cdelay.dsp>;
<c:\adi_dsp\macros\tap.dsp>;
<c:\adi_dsp\macros\tapin.dsp>;
<c:\adi_dsp\macros\dot.dsp>;
<c:\adi_dsp\macros\cfir.dsp>;
<c:\adi_dsp\macros\ccan.dsp>;
<c:\adi_dsp\macros\cdir.dsp>;
<c:\adi_dsp\macros\wavgen.dsp>;
<c:\adi_dsp\macros\zero.dsp>;
{wrapup}
The files begin.dsp and end.dsp in the directory c:\adi_dsp\macros are the
beginning and ending parts of the talkthru program in the EZ-KIT Lites Reference
Manual [2] and the mic2out.dsp program found in [3].
References
[1] S. J. Orfanidis, Introduction to Signal Processing, Prentice Hall, Upper Saddle
River, NJ, 1996.
[2] ADSP-2100 Family EZ-KIT Lite Reference Manual, Analog Devices, Norwood, MA,
1995. Available also from www.analog.com.
[3] Analog Devices BBS (617) 461-4258, or anonymous ftp site ftp.analog.com, or
web site www.analog.com. The ezld.com utility is included in the file ezkforum.zip in the subdirectory pub/dsp/ezktil of the ftp site.