top of page

DSP instruction usage:

Aim of the lesson:

In this lesson we will learn how to use the DSP specific instructions of the dsPIC33EP family DSCs. The MCU and DSP instructions can be found here.

Calling asm functions from C:

In the first lesson we created a basic setup for the dsPIC in C. We need to learn how to call writen asm routines from C, in order to connect the time critical parts (asm) with the normal program sequences (C).

​

Firts let's create a new assembly (.s) file, which will add two unsigned 8bit integers and return the value.

Between the .nolist  and the list section we can put our local includes (.inc, and .h) from the .text to the .end we can write our code.

Declaring _add_uints a global will make it usable in other files.

The push instruction will store the content of the given register in the stack (a LIFO register). A push w0 instruction is equivalent  to mov w0, [w15++], and a pop w0 instruction is equivalent to mov [--w15], w0

The W registers contain the variables used in the function header (we have two 8b values so w0 and w1 will be used).

The return value of the function (or the address pointer in case of an array) is always stored in W0.

We can add our two numbers keeping the above mentioned registers in mind. The operation is W8 = W1+W2, and since W0 returns the result, we move the value from W8.

​

It's very important to store and restore temporal registers in assembly (with the above used stack operations). This way we can be sure that our registers will contain the appropriate values - even if we use them only once (we can't see how the optimizer uses the registers after compilation).

​

In the main header we add a new function handle extern char add_uints(char,char). This handle will use our function defined in the .s file. We can simply write char c = add_uints(1,2); and debug our code using the Simulator (the SFR will contain the W registers).

DSP instructions:

We need to create some starting data to test the DSP instructions. Let's use the first 10B data in both X and Y memory and store 0x1221 values in the X memory. The near X memory starts from 0x1000 address, and the Y memory starts from 0x5000 address, so we can allocate the variables in our main header as:

The variable in the X memory can be initialized in a simple cycle.

The handle of the DSP tester function will be extern void dsp_instr_test(uint16_t* Xmem, uint16_t* Ymem); This way in the assembly code we will have the starting address of the Xmem variable in W0 and the starting address of Ymem variable in W1.

​

We need to add the xc16 compiler header to our assembly file: in the nolist section add .include "xc.inc".

We need to define our _dsp_instr_test function as global, and store the CORCON, W4, W5 registers in the stack.

Load Accumulator (LAC):

The LAC instruction loads the content of a given register or literal into the given accumulator, and optionally shifts the value (positive number is right shift, negative number is left shift).

LAC [W2], A - will store the value at W2 address in Accumulator A

 

Store the first value from X memory in Accumulator B shifting its value to left by 3 first.

Repeat the process with saturation enabled (BSET CORCON, #SATB).

What will this saturation bit do?

Addition (ADD):

The ADD instruction adds working registers literals and accumulator contents, and stores the result in the given accumulator.

​

Add the content of the A and B accumulators with and without saturation. What is the result?

Clear (CLR):

The CLR instruction clears one file or working register or accumulator, meanwhile some prefetch options are available, ex.:

CLR B, [W8]+=2, W6, [W10]+=2, W7, [W13]+=2, which clears Accumulator B; moves the data containd in the X memory at address W8 to W6, post increments the address; moves the data contained in the Y memory at address W10 to W7 and post increments the address; moves the content of Accumulator A to the address W13 and increments the address - all in one instruction cycle.

 

Try out this instruction giving some initial values to the registers.

Euclidean distance (ED, EDAC):

The ED instruction computes the square of a given register, and computes the difference of the given prefetch values:

​

ED W4*W4, A, [W8]+=2, [W10]+=2, W4 - Test it. What is the result?

​

The EDAC instruction creates the same square as above, but adds the result to the given accumulator:

​

ED W4*W4, B, [w9]+=2, [W11+W12], W5 - Test it. What is the result?

Multiplication (MPY):

The MPY instruction multiplies two working registers, and stores the result in an accumulator. Optionally prefetch operations are available.

​

Test the MPY in integer mode (with signed and unsigned values):

BSET CORCON,#IF - sets the integer mode.

​

mov #0x2000,W4

mov #0x4000,w5

mpy W4*W5,A

​

Test the same multiplication in fractional mode (with signed and unsigned values).

The MPY.N instruction stores the negative of the multiplication result. Try it out.

Multiply and Accumulate (MAC):

The MAC instruction multiplies the contents of two working registers, optionally prefetch is available for operands in preparation for another MAC type instruction and optionally can store the unspecified accumulator results.

​

Let W4 = 0xA022, W5 = 0xB900, W8 = 0x0A00, W10 = 0x1800, Data at 0x0A00 = 0x2567, Data at 0x1800 = 0x909C, set CORCON to 0x00C0 - fractional values, normal saturation

​

MAC W4*W5, A, [W8]+=2, W4, [W10]+=2, W5, W13 - Try it out, what is the result?

​

What is the difference if CORCON is 0x00D0 (super saturate)?

Other important instructions:

MSC - same as MAC with subtraction

NEG - negates the content of the accumulator.

SAC - stores the content of the accumulator - Try to shift the content of A by 2 to the right, and store the high word in W4.

SFTAC - performs an arithmetic shift on the accumulator in the range of [-16,16].

SUB - subtracts literals from registers, or accumulators.

​

Don't forget to pop the contents of the stack pointer and add return to the end of the assembly function.

bottom of page