ftp.nice.ch/peanuts/GeneralData/Documents/dsp/DSPTutorial.tar.gz#/DSPTutorial/08_PitFalls.rtf

This is 08_PitFalls.rtf in view mode; [Download] [Up]

Written by J. Laroche at the Center for Music Experiment at UCSD, San Diego California. December 1990.


IMPORTANT REMARKS, PITFALLS and TROUBLE SHOOTING


SETTING-UP and CONTROLLING STREAMS, BOOTING, REBOOTING the DSP


· If SNDAcquire() returns "Cannot access hardware resources", check that the sound device is not currently busy (you can use the unix command dspbeep which generates a sound on the dsp and plays it.). The second possibility is that the dev_port and/or owner_port you passed were not initialized to zero. In that case (and it seems to be a bug) the function doesn't work properly.

· A number of stream set-ups do not work on the Version 2.0 pre-release I've tested. These include
    - SNDDRIVER_STREAM_FROM_DSP and SNDDRIVER_STREAM_TO_DSP won't accept sample size over 2.
    - SNDDRIVER_STREAM_SNDIN_TO_DSP won't even set-up.
    - SNDDRIVER_STREAM_FROM_SNDIN_THROUGH_DSP sets-up but does not seem to work.
    - It's still impossible to record from the DSP to the memory and send the sound to the DACs at the same time (that is, listen to what you're recording...) This is very annoying.
Some of these bugs might be fixed in the final 2.0 release. Once again, this concerns only the Version 2.0 fuchsia pre-release.

· If snddriver_stream_setup() returns a "Bad size" error, check that the DMASIZE you passed is correct (that is, a power of 2 great than or equal to 16), and that the sample size is 2 (sizeof(short)).

· Always make sure that the DMA size is the same in the C program and in the DSP Assembly program. If it's not, the DMA transfer cannot work properly and no sound will be heard.

· When sending samples from the DSP to the host, make sure the sample_size in snddriver_stream_setup() is 2: don't forget that sound samples are 2 bytes long.

· If you get drop-outs or blanks when playing a sound, there might be several causes:
    - If you're playing a sound through the DSP without using DMA from the host to the DSP, the driver cannot send enough samples to satisfy the DACs (when the sound is 44100 stereo.) In that case, the remedy would be to use DMA. (see 07_DMA_To_DSP.)
    - If you're just playing a sound to the DACs, check that the low_water and high_water values in the stream set-up are high enough. In practice, low_water=48*1024 and high_water=512*1024 give pretty reliable results.

· If the stream controls have no effect, check that the tag you used in the snddriver_stream_start_writing() function is the same as the one in snddriver_stream_control(). 

· If you don't get any message from the driver when you think you should, check that:
- You allocated a reply_port.
- You asked for the correct messages in your snddriver_stream_start_writing() function, and passed the correct reply_port.
- You allocated a message header and set its local port to reply_port and its size to MSG_SIZE_MAX (do it for each message).
- msg_receive() returns a zero value (indicating that a message has been retrieved.)
- You provided a function in your reply_handler corresponding to the kind of message you asked.

· Do not boot the DSP before you set-up the stream from the DSP to the DACs otherwise you won't get any sound. This is because in our implementation, as soon as the DSP is booted, it requests a DMA transfer by passing a special number to the driver ($50001).  If the driver has not been previously initialized, it won't recognize the DMA request, and DMA protocol will never start. If you need for some reason to load and boot the DSP before initializing the stream (for example if you're changing the stream without re-loading the DSP), you should make sure the DSP will not send a DMA request until the driver is initialized. A way of doing that is to use the stop_flag in the DSP code: initialize the stop_flag to block the DSP before requesting the DMA transfer, waiting for its bit #0 to go low, and send a "60" host command from the C program when the driver has been initialized, to reset bit #0 of stop_flag. This ensures that the DMA starts only after initialization of the driver.

· If you need to change your DSP program on the fly (without re-initializing the stream), for example to switch from one effect to another, you can reboot the DSP using the SNDBootDSP() function, but you need to make sure that:
- you stop the DMA in a clean way before rebooting,
- your interrupt vectors are correctely set (each two words).
Stopping the DMA cleanly means to stop the DMA stream after a full DMA buffer. This can be done by calling the host command 60 (in our program) which sets the bit #0 in the stop_flag, and therefore causes the DSP to wait infinitely before sending a DMA request (till the cows come home, or more precisely, till that bit is reset). When you reboot the DSP, DMA will restart properly. If you just reboot the DSP without first stopping the DMA stream, the DSP might be  rebooted in the middle of a DMA buffer, which would wedge the driver.
Each interrupt vector is two words long (that's why their addresses are only even.) You need to make sure every couple of words is correctely set (eventually with a nop instruction). This is because when you reboot the DSP, the program memory is not cleared, and some instructions previously put in a interrupt vector (by the previous DSP program) might still be in the memory and alter the correct functionning of the new interrupt vectors. For example, suppose in a first DSP program, you have 

	org	p:40			
	bset	#1,x:0
	bset	#1,x:1

If you reboot the DSP with a new program in which the same interrupt vector is:

	org	p:40			
	bset	#0,x:0

Then, the DSP program memory will contain

	org	p:40			
	bset	#0,x:0
	bset	#1,x:1			; remains from the previous DSP program!!!

Which is not at all what you want! To avoid that, the second interrupt vector should have been:

	org	p:40			
	bset	#0,x:0
	nop					; To avoid that kind of mess!!
	
	or
	
	org	p:40			
	bset	#0,x:>0		; To force the X:address to be long and the instruction to be 2 words long.
	

RECEIVING / SENDING DATA from/to the DSP 

· If you don't receive any sample or sound from the DSP, a good test consists in replacing the reception of samples from the SSI port by a simple sine wave generator. You'd have to add the two initializations:

    move	#>$100,R1	; Start address of the sinewave table in Y memory
    move	#>$FF,M1	; Length of the table - 1
    
and replace the code inside the DMA loop by:

_send
	move	y:(R1)+,Y0
	move	#>$7FFF,X0
	mpyr	X0,Y0,a	
_wait

You should then get a sine wave through the DACs. If you don't, something has to be wrong in the DMA protocol (DMA size?, sample size?), or in the initialization of the DSP. Check that m_hcr and sr are correctly set (like in our example.) Also,  check that the bits #m_sre and #m_srie in x:m_crb are set if you intend to receive samples from the SSI port (digital ears.) Did you use a rts instead of a rti (see below)? Is the DMA size the same in the DSP program and in the C program? Are you using a jump instruction to the end of your DMA loop (see below)?

· Returning from an interrupt: if your interrupt vector is a jsr instruction, like some in our program, the subroutine you're jumping to should terminate with a rti instead of a normal rts. Putting a rts instead of a rti just wedges the DSP.

· When you pass data from the host to the DSP (like the value of the volume in our case), and if you're using host commands make sure the host command you send corresponds to the interrupt you implemented on the DSP program memory. Remember that the address of the interrupt on the DSP program memory is always even, and must be equal to twice the number passed in the host command. For example, if you implemented a interrupt at the address 46 (46, NOT $46 = 70!!!!!)

	org	p:46					
	jsr	getData

you should send the following command to trigger the interrupt:
 
	k_err = snddriver_dsp_host_cmd(cmd_port,23,SNDDRIVER_LOW_PRIORITY);

· When you send a value to the DSP via snddriver_dsp_write() make sure the DSP reads it! If you don't, the third data you'll send will wedge the driver and the DMA flow to the DACs will stop. This happens for example if you didn't send the right host command after sending a value to the DSP: the DSP will jump to a wrong address, probably do nothing and resume its previous task, but the value sent by the host has not been read, and the second next call to snddriver_dsp_write() will stop the DMA flow.

· Here is the way the host and the DSP synchronize their communication, in the case of Host->DSP data passing. It uses the HRDF bit in the host status register (hsr):
- The host checks that HRDF is clear then writes a value in hrx. This sets HRDF.
- The DSP has to wait until HRDF is set (meaning that a datum is available) and then reads hrx. This automatically resets HRDF.
- The host has to wait until HRDF is reset (meaning the DSP has read the value) before writing another datum, and so on.
Always remember that as soon as you access (not necessary read) the m_hrx register, the host will feel free to write another datum on it. For example, suppose your application sends two data to the DSP (using snddriver_dsp_write() with two values), and suppose your DSP code needs to test the sign of the first one and get the two values. You might write it this way:

_waitFirst
    jclr	#m_hrdf,x:m_hsr,_waitFirst		; to wait for a value to arrive
    btst 	#23,x:m_hrx				; test the sign of the value in m_hrx
    ...								; some code following the bist test
    move	x:m_hrx,a					; puts hrx into a
_waitSecond
    jclr	#m_hrdf,x:m_hsr,_waitSecond    	; to wait for the second one
    move	x:m_hrx,b					; puts hrx into b
    
 The problem is that as soon as you access m_hrx, by the instruction btst  #23,x:m_hrx, the driver understands that he can write the next value on the hrx register, and very likely, the value you'll put in the register a will be the second value the host sent! This is because btst resets HRDF, just like move does. The proper program could be

_waitFirst
    jclr	#m_hrdf,x:m_hsr,_waitFirst		; to wait for a value to arrive
    movep	x:m_hrx,x:(R0)			
    btst 	#23,x:(R0)				; test the sign
    ...								; some code following the bist test
_waitSecond
    jclr	#m_hrdf,x:m_hsr,_waitSecond    	; to wait for the second one
    move	x:m_hrx,b					; puts hrx into b

· Format conversion between the host and the DSP: The host processor represents integer data by 2,3 or 4 bytes long right-justified words. The DSP represents decimal values with 3 bytes long left-justified words and ints by 3 bytes long right-justified words: for example

    256  = $100 in hexadecimal,  is represented by $000100 (left byte 00, middle byte 01 right byte 00);
    	Right-justified means that $100 = $0100 = $0000100... in integer binary representation.
    2^^-1 = .5 = $4 in hexadecimal,  is represented by $400000 (left byte $40, middle byte 00, right byte 00);
    	Left-justified means that $4 = $40 = $40000000000... in decimal binary representation.

Therefore, one needs to be careful when passing data between the DSP and the host:
- From the host to the DSP: The host will write all its data right-justified. In other words, a one byte datum will be written on the rightmost byte of the DSP register, a short int will occupy the two lower bytes of the DSP register, and only the three lower bytes of an int will be written on the DSP register. This means that the maximum int value you can pass to the DSP is 2^^23.
- From the DSP to the host: The host will read right-justified data. Therefore, if the host is set-up to read a one byte datum, it will only read the lowest byte of the DSP register, if it's expecting a short int, it will read the two rightmost bytes of the DSP register, and if it's expecting an int, it will read the three bytes in the DSP register and sign extend the result to four bytes.
Therefore, when the DSP is sending data to the host, it should rescale them according to what the host expects, to avoid clipping. For example, the sine wave table on the DSP rom is made of three byte decimal values left-justified. If you want to pass them to the host as shorts (samples) you need to shift them one byte right:

    2^^-1 = .5 = $4 is represented by $400000 on the DSP, but should be passed as $004000 to the host since the host is only reading the two right bytes. To shift a value one byte right, just multiply it by $008000 (or $007FFF to avoid clipping.) The SSI port also copies left-justified samples. See 05_Dig_Ears for an example of rescaling SSI samples.

· If you need to convert values from float to DSP compatible int, you can define your own macros or use the functions provided in /usr/include/dsp/DSPConversion.h. To convert a float between -1 and 1 to a DSP int, just multiply it by 8388607 (2^^23 - 1) and convert it to an int. Do the converse to convert the other way around.
    
· When the host passes two word data to the DSP, it does not sign extend the value before passing it to the DSP (except if you passed them using snddriver_dsp_write()). Suppose you're using a stream set-up with a stream from the host to the DSP (either DMA or not) with a sample size of 2, and start writing on the stream to pass data to the DSP. The data are two byte words on the host side, and will be written on the 3 byte DSP register. The problem is that the driver writes the data on the two least significant bytes of the DSP register (the two rigth ones) and does nothing else (leaves the left byte to zero). If the value is negative, the DSP won't understand it:

    host value:	$010D  =  269	->	DSP value	$00010D  =  269
    host value:	$FE37  =  -457	->	DSP value	$00FE37  =  65079   instead of  $FFFE37  =  -457

In practice, this problem will manifest itself by a strong distortion whenever the DSP has to perform computations on the samples. The only way to avoid this problem is to perform sign extension on the DSP side (write FF in the most significant byte when necessary, i.e. when the highest bit in the host value is set.) The DSP code could be:

	movep	x:m_hrx,x:(R0)			
	jclr 	#15,x:(R0),_no_extension	; Test the sign of the incoming value
	move	x:(R0),a			; Suppose x:(R0) = 00FE37, now a = 00 00FE37 000000
    	move	#>$FF,a2			; Now a = FF 00FE37 000000
	move	#>$FF0000,X1		
	or		X1,a				; Sign Extention: a = FF FFFE37 000000
	move	a,x:(R0)			; And x:(R0) = FFFE37, the correct value!
_no_extension

Note that we first store the host value, then test its bit #15. We could test the bit #15 directely on the m_hrx register, but then the driver would write another value, assuming we have read hrx (see above.) This software sign extension is not necessary when you use snddriver_dsp_write(). In that case, the driver automatically sign extends the value and writes three bytes on the DSP register. Samples received from the digital ears (SSI port) don't need to be sign extended since they are written on the two left most bytes of the SSI register.

· Interrupting an interrupt: you can set the bits in the interrupt priority register to control the respective levels of host and SSI interrupts. See Interrupt Priority Structure, page 8-8 in Motorola's DSP user's manual. In our program, we set host = 2 and SSI = 2, which means that a SSI data received cannot interrupt a host interrupt, and vice versa. Most of the time, it's better not to enable interruption interrupt. For example, getting a datum from the host migth require a small amount of computation (rescaling, for example) during which it's best not to be interrupted...
You may find necessary to allow interruption interrupt. In that case, you'd just need to change the values of the bits in the IPR register.

· On the DSP side, if you're receiving data from the SSI port, you need to set

	bset	#m_sre,x:m_crb
	
in order to receive data from the SSI port and 

	bset	#m_srie,x:m_crb

to get SSI received interrupts (address $0C) each time a sample is received.

· There is another interrupt address related to the process of receiving sample from SSI port:

	org	p:$E			;SSI data received exception interrupt
	jsr	except

This interrupt gets called when the SSI port receives a sample before the previous received one has been read by the DSP program. For example, if the SSI data received interrupt (address $000C) calls a subroutine that is too long (takes too long a time), the SSI port might receive another sample (triggering a SSI data received interrupt that would be put on hold because the DSP is already processing an interrupt) and yet another one before the subroutine returned: the second received sample would trigger a "SSI data received with exception" interrupt (address $000E) since the previous received sample has not been read yet.
From that moment on, all the samples received on the SSI port will trigger "SSI data received with exception" interrupts instead of normal "SSI data received" interrupts. In other words, once one sample has been received with exception, all the following ones will generate exception interruptions until you reset the SR register. To reset this state to the normal state, you need to do the following:

	movep	x:m_sr,a
	movep	x:m_rx,a		

First read the status register, and then the RX register. See Receiver Overrun Error flag, page 7-68 in Motorola's DSP user's manual. In practice, you shouldn't come accross this problem. It might happen if you implement a procedure in the "SSI data received" interrupt that is too long to be real-time (i.e. which takes longer to perform than the time between two incoming samples, about 11 ms.) In that case, the SSI port will receive more samples than your DSP application might possibly absorb, and you'll eventually get "SSI data received with exception" interrupts.

· Ariel Digital Microphone: For some reason, the DSP set-up used to receive samples on the SSI port won't work for Ariel's digital microphone. If you need to use Ariel's digital microphone, then you'll have to find out about the way samples are sent by this device and change the DSP set-up accordingly.


DSP PROGRAMMING PITFALLS

· If you want to use BUG56, the DSP debugger by Ariel (very useful) you need to discard some of the interrupt vetors initializations in you DSP program and start you main program at an address above $96 (BUG56 implements a monitor program on the lower end of the DSP program memory.) In other words, keep away from the first $96 words in the DSP memory. BUG56 is in /NextDeveloper/Apps.

· Loading an immediate value into a register:

    move	#$1F,X0			; X0 = $1F0000
    move	#>$1F,X0			; X0 = $00001F

don't do the same thing. 
The first instruction means "put the short value (2 bytes) 1F into the register X0". As a result, X0 now contains $1F0000.
The second instruction means "put the long value (6 bytes) 1F into the register X0". The result is: $00001F. The ">" sign forces the value to be interpreted as a long integer.

· Multiplying two integers:
If you need to multiply two integers (for example X0 = 245 and Y0 = 13) you can use the mpy function, (not mpyr!!) only with extra care, since mpy understands numbers as signed decimal numbers, and not signed int! For example,

    move	#>245,X0			; X0 = 245
    move	#>13,Y0			; Y0 = 13
    mpy	X0,Y0,a			; a = (a2 = 0, a1 = 0, a0 = 3185)
    mpyr	X0,Y0,b			; b = (b2 = 0, b1 = 0, b0 = 0)
    
The result in a is correct, but must be fetched in a0 and not in a1. The result in b is incorrect, since it has been rounded to fit in a 3 byte word.

· Do loop. Certain instructions are forbidden at the end of a do loop. If you use them, the assembler will tell you. There's something it won't tell you about: you can't jump to the end of a loop. Consider the following example:

	    do	#2,_end_do
	    ...
	    jmp	_end_do
	    move	#$F3,a 
	_end_do

This example won't work: the loop will be executed only once instead of twice. A correct example would be:
	
	    do	#2,_end_do
	    ...
	    jmp	_end_do_bis
	    move	#$F3,a 
	_end_do_bis
	    nop
	_end_do


· Interpolating: when you write an interpolating oscillator for example, it is convenient to keep the phase in a XY word (6 bytes long). The first 3 bytes might contain the integer phase (address of the value in the sine table) and the 3 other bytes the decimal phase. Here's a simple program showing how to implement interpolation.
l:phase contains the current phase in the XY memory, x:increment the value of the increment, and x:mask the size of the sine table - 1. R0 is initialized to the 

	move	#>$100,R0			; Start address of the sine table
	move	#>$FF,M0			; length - 1
	move	M0,x:mask
	
sine
	move	l:phase,Y		; The phase is a long word
	move	Y1,N0 
	nop 
	move	y:(R0+N0),X0			
	tfr		Y0,b			(R0)+
	move	y:(R0+N0),a
	sub		X0,a  			(R0)- 
	lsr		b  a,X1
	tfr		X0,a 			b1,X0
	mac	X1,X0,a 		x:increment,X1 
	tfr		Y1,b    		#>$80,X0
	move	Y0,b0  
	mac	X1,X0,b		x:mask,X1
	and		X1,b 	
	move	b,l:phase
 
The idea is simple: the higher part of the phase (Y1) is used to fetch the upper and lower values for the following interpolation. The difference is calculated, and needs to be multiplied by the fractional part of the phase. Here's the trick: 
The fractionnal part of the phase (in Y0) is an unsigned positive decimal number ranging from 0 to 0.999999. ($000000 to $FFFFFF). For example, a total phase of (Y1 | Y0) = $000020 | $800000 should be interpreted as follow: We're between the $20 th and the $21 st values in the table, and $800000 means that the fractional part is .5.
Now, the DSP only understands signed decimal numbers, and Y0 = $800000 is interpreted as Y0 = -1 !!!! 
To transform our unsigned positive number into a signed number with (almost) the same value, we need to shift the bits one position right, which is done by  the lsr instruction. Now Y0 = $400000 correctely interpreted as .5.

To update the phase, we simply add the increment after multiplying it by a rescaling factor (otherwise, the increment would be added only to the integer part of the phase.) Here, multiplying the increment by #>$80 shifts the value two bytes right: the most significant byte of the increment is added to the integer phase, and the two less significant bytes are added to the decimal phase.

	

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.