The organization of memory and the mode of its use are called the memory model. Understanding the memory model is essential for programming. The memory is divided into the program memory and data memory. The program memory is further divided into the user program space and the user configuration space. The data memory is the space of memory locations used by a program for saving different data required for the program execution. The size of the memory space is different for different members of the dsPIC30F family.
The dsPIC30F microcontrollers have 4M 24-bit (3 bytes) program memory address space. This does not mean that the the size of the memory is 12MB (3x4M=12MB), but that is can generate 4M(4x220) different addresses.
The size of the program memory of a dsPIC30F4013 device is 16K words, i.e. 3x16K=48KB. A program can have 16K instructions less the number of auxiliary locations (interrupt table and similar).
The structure of the program memory is given in Fig.8.1 The first two locations are reserved for defining the beginning of a program. The execution of a program starts from
Fig. 8.1 Program memory of dsPIC30F4013 microcontroller
The program memory is accessible only via even addresses. An attempt to read an odd address will result in CPU trap and device reset. Interrupts and traps are described in Chapter 3.
The main program start at the location $000120. The location $000000 contains GOTO instruction, and location $000002 contains value $000120. During each reset of the device, from the location $000000 GOTO instruction is read and from the location $000002 the address is read where to jump (in this case $000120). The program starts execution from the location $000120.
After the first two locations is the interrupt vector table serving, as explained in Chapter 3, for specifying the locations where the interrupt subprograms are. It consists of 62 locations (see Chapter 3). The next two locations ($000080 and $000082) are reserved for internal purposes. Then, there is the altenative interrupt vector table with another 62 locations. At the address $000100 is the start of the space in the program memory for the main program and subprograms. The maximum address of the program space is $0007FFE (total of 16K locations). After this address is the program memory configuration space.
Data memory (RAM)serves for storing and keeping data required for the proper operation of the programs. Depending on the program in progress, it can, but does not have to, be split into two sections. For DSP2 instruction set the data memory is considered to consist of two sectioins. The two data spaces are accessed by using two address generation units and separate data paths, i.e. two data can be read or written simultaneously. Chapter 11 gives more details. For other instructions the data memory is considered unique. Structure of the data memory of the dsPIC30F4013 and dsPIC30F6014A devices is shown in Fig. 8-2.
Fig. 8-2 Data memory of dsPIC30F4013
The size of the data memory, similarly to the program memory, depends on the model of dsPIC devices. For dsPIC30F4013 device the data memory has 64K. The addresses are 16-bit, i.e. no more than 64K addresses can be generated. All data memory addresses are even. An attempt to access an odd address will result in device reset. The first 2K locations are reserved for the Special Function Registers (SFR) and general purposes (1K 16-bit locations). These registers contain the control and status bits of the device. From the address $0800 the RAM is divided into two sections, X and Y. This division is essential only for DSP instructions. For other applications only the total size matters (e.g. for dsPIC30F4013 this size is 2KB). Writing to the RAM is performed as if this division did not exist for both DSP instructions and others. Only when reading DSP instructions this division is used to read two memory locations simultaneously. This access to the memory speeds up considerably the execution of DSP instructions which have been optimized for signal processing, which most of the time requires calculation of the sums of products of two arrays (the so called MAC istructiuons - Multiply and Add to Accumulator). This requires fast reading of both operands to be multiplied and the result added to the previous value (accumulation).
Modulo, or circular addressing provides an automated means to support circular data buffers using hardware. The objective is to remove the need that the software has to perform data address boundary checks when executing tightly looped code as is typical in many DSP algorithms. Any W register, except W15 (used as the stack pointer), can be selected as the pointer to the modulo buffer. Also the use of W14 register as the stack frame pointer register is recommended.
A frame is a user defined section of memory in the stack that is used by a single subroutine (function or procedure). The length of a circular buffer used in modulo addressing is not directly specified. The maximum possible length is 32K words. A location in the buffer can be either 16-bit (16-bit buffer) or 8-bit (8-bit buffer). However, modulo addressing always operates with 16-bit buffers, therefore the length of the 8-bit buffers has to be even. Another restriction on the 8-bit buffers is that they can not be in the Y space, but only in the X space or in the program memory.
Modulo addressing is specified by the five registers: XMODSRT, XMODEND, YMODSRT, YMODEND, and MODCON. The register XMODSRT specifies the starting and XMODEND ending address in a circular buffer in the X space, if used. The register YMODSRT specifies the starting and YMODEND ending address in a circular buffer in the Y space, if used. The register MODCON is used for enabling/disabling modulo addressing and for its configuration. The structure of the MODCON register is shown at the end of this chapter. The registers XMODEND and YMODEND contain the address of the last occupied location in the buffer. The address of the last occupied location is always odd in both 16- bit and 8-bit buffers.
{dsPIC30F6014A}
program Moduo1;
var
buff:array[10] of Word //Circular buffer
i:integer;
begin
TRISD:=0;
for i:=0 to 9 do //Init buff
buff[i]:=i;
YMODSRT:=@buff; //YMODSRT points to the first element of buff
YMODEND:=@buff+19; //YMODEND points to the end address of buff
MODCON:=$80AA; //Moduo address Y space
nop; //After changing MODCON forced nop is recommended
W10:=@buff; //W10 points to the first element of buff
while TRUE do
begin
asm
MOV [W10++], W5 //Copy current element to W5,prepare for next
MOV#$02D6, W4 //W4 points to LATD
MOV W5, [W4] //Fill LATD with item
end;
Delay_ms(200);
end;
end.
Modulo addressing hardware is enabled only with indirect addressing ([W10++]). After the registers for configuring modulo addressing have been set, indirect addressing must not be used. Since the next instruction can be compiled by using indirect addressing, it is recommended that after setting these registers NOP (non-operation) is added. This ensures that the next instruction is executed correctly.
Bit-reversed addressing is used for simplifying and speeding-up the access to the arrays in FFT (Fast Fourier Transform) algorithms. Like in modulo addressing, this part of hardware allows that the part of the program which calculates the elements of an array be skipped in order to simplfy the program and speed-up its execution. This addressing method is possible only in the X space and for data writes only. It is used with the pre-increment or post-increment addressing modes. Modulo addressing and bit-reversed addressing can be enabled simultaneously using the same register. Bit-reversed addressing operation will always take precedence for data writes and modulo addressing for data reads. Therefore, modulo addressing restrictions will aplly when reading data and bit-reversed addressing restrictions when writing data.
Bit-reversed addressing enable and the size of the bit-reversed data buffer are specified by the register XBREV. Bit-reversed addressing is assigned to one of the W registers specified by the MODCON register.
Fig. 8-3 Block diagram of bit-reversed addressing of a 16-word array
Fig. 8-3 shows block diagram of bit-reversed addressing of a 16-word array. The last bit is always zero because the addresses have to be even (access to 16-bit data). The sequence of bit-revessed addresses is given in Table 8-1.
| Normal Address | Bit-reversed | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| R1 | R2 | R3 | R4 | decimal | R1 | R2 | R3 | R4 | decimal |
| 0 | 0 | 0 | 0 | 00 | 0 | 0 | 0 | 0 | 00 |
| 0 | 0 | 0 | 1 | 01 | 1 | 0 | 0 | 0 | 08 |
| 0 | 0 | 1 | 0 | 02 | 0 | 1 | 0 | 0 | 04 |
| 0 | 0 | 1 | 1 | 03 | 1 | 1 | 0 | 0 | 12 |
| 0 | 1 | 0 | 0 | 04 | 0 | 0 | 1 | 0 | 02 |
| 0 | 1 | 0 | 1 | 05 | 1 | 0 | 1 | 0 | 10 |
| 0 | 1 | 1 | 0 | 06 | 0 | 1 | 1 | 0 | 06 |
| 0 | 1 | 1 | 1 | 07 | 1 | 1 | 1 | 0 | 14 |
| 1 | 0 | 0 | 0 | 08 | 0 | 0 | 0 | 1 | 01 |
| 1 | 0 | 0 | 1 | 09 | 1 | 0 | 0 | 1 | 09 |
| 1 | 0 | 1 | 0 | 10 | 0 | 1 | 0 | 1 | 05 |
| 1 | 0 | 1 | 1 | 11 | 1 | 1 | 0 | 1 | 13 |
| 1 | 1 | 0 | 0 | 12 | 0 | 0 | 1 | 1 | 03 |
| 1 | 1 | 0 | 1 | 13 | 1 | 0 | 1 | 1 | 11 |
| 1 | 1 | 1 | 0 | 14 | 0 | 1 | 1 | 1 | 07 |
| 1 | 1 | 1 | 1 | 15 | 1 | 1 | 1 | 1 | 15 |
Table 8-1 Bit-reversed address sequence (16-entry)
Table 8-2 shows the dependence of the value loaded into the XBREV register on the size of the bit-reversed data buffer.
| Buffer Size (16-bit word) | XBREV (XB<14:0>) |
|---|---|
| 1024 | 0x0200 |
| 512 | 0x0100 |
| 256 | 0x0080 |
| 128 | 0x0040 |
| 64 | 0x0020 |
| 32 | 0x0010 |
| 16 | 0x0008 |
| 8 | 0x0004 |
| 4 | 0x0002 |
| 2 | 0x0001 |
Table 8-2 Bit-revesrsed address modifier values
By using bit-reversed addressing when calculating FFT, the process is several times faster. For the dsPIC30F4013 device the size of the bit-reversed data buffer is limited to 1K words.
{dsPIC30F6014A}
program BitRev1;
var
InBuff:array[16] of Word; //input buffer
BrBuff:array[16] of Word; //bit-reversed buffer
i:integer;
begin
TRISD:=0; //PortD is output port
TRISB:=0;
for i:=0 to 15 do
begin
InBuff[i]:=i; //Init input buffer
BrBuff[i]:=0; //clear bit-rev buffer
end;
XBREV:=$8008; //enable bit-reversed addressing for 16-word buffer
MODCON:=$01FF; //setup MODCON for W1 bit-rev register
nop; //after changing MODCON forced nop is recomended
W0:=@InBuff; //W0 points to the first element of input buffer (InBuff)
W1:=@BrBuff; //W1 points to the first element of output buffer (BrBuff)
asm
repeat #15
mov [W0++], [W1++] //fill output buffer (BrBuff)
end;
XBREV:=0; //disable bit-reversed addressing
nop;
while true do //SHOW bit-rev buffer on PORTD
begin
for i:=0 to 15 do
begin
LATB:=i;
LATD:=BrBuff[i];
Delay_ms(2000);
end;
end;
end.
A stack is a section of memory serving for temporaty storage of data (e.g. while calculating complex expressions). Its most important task is keeping the states of significant registers during jumps to subprograms, interrupts, traps, etc. During a jump to a subprogram in this part of the memory are kept the parameter values (if any) at the t ime of calling a function or a procedure, value of the PC register (the place reached during the execution of a program), and the frame register W14. The values of the PC and W14 registers are copied to the stack automatically, increasing the value of the W15 register by 6 (three times by 2). The compiler takes care to copy the parameters to the stack by adding a part of the code required for copying the parameters to the top-of-stack on each user call of a function or a procedure.
A stack is a section of memory which is usually accessed sequentially. The access is possible, of course, to any memory location, even to the locations constituting the stack, but such concept is very seldom used. An increase or a decrease of the stack, however, can be done only by a sequential access. When a datum is copied to the stack, it is pushed to the top-of-stack. Only the value from the top-of-stack can be read by the W15 register. Thus a stack is a LIFO (Last In First Out) buffer. In order to know at each moment which address is read or which address is written, one of the registers is reserved as the stack pointer register. This is the W15 register. Therefore, when a datum is copied to the stack, it is written to the location pointed by the W15 register and then the W15 register is increased by 2 to point at the next free location. When a datum is read from the stack, the value of the W15 register is at first decreased by 2 and then the value from the top-of-stack is read.
How does this work in practice? The following example gives a program consisting of the main program and one procedure.
program MemoryTest; var m:word; procedure MyProc1(a:Word); var i:word; begin i := a+2; end; begin TRISB :=0; m := 3; nop; MyProc1(m); m := 2; end.
The main program begins by executing the instruction TRISB :=0;. After that, variable m is allocated the value 3. Then, the procedure is called. What happens at that moment? The compiler has generated a code for copying to the stack the variable m (Fig. 8-4a) and then jumps to the memory address where the subprogram is located.
Fig. 8-4a Stack before entering subprogram
The hardware automatically copies to the stack the program register PC (Program Counter) in order to determine the location from which the execution of the program continues after the subprogram is done, Fig. 8-4b. Since the width of the PC register is 24-bit, two memory locations are required for copying the PC register. The lower 16 bits (PCL) are copied first and then the higher (PCH) 8 bits (extended to 16 bits).
Fig. 8-4b Stack after jump to subprogram (W15=0x806, W14=xxxx)
After the PC register is saved, the hardware also copies and saves the W14 register (frame register) and then writes into it the current value of the W15 register, Fig. 8-4c. This saves the information where the last location used by the main progam is. Whatever the subprogram would do with the section of the stack after this address will have no influence on the execution of the main program. Therefore, the W14 register is the boundary between the local variables of the subprogram and the parameters pushed in the stack from the main program. In addition, this allows to find the address where the parameter value valid at the time of calling the subprogram is. This is done simply by subtracting from W14 the number of locations occupied by the PC and W14 registers at the moment of jumping to the subprogram.
Fig. 8-4c Stack after entry to subprogram (W15=0x808, W14=0x808)
Upon jumping to the subprogram (procedure in this example), the compiler has the task of providing the locations required by the local variables. In this example the local variable is i of the type word. Fig. 8-4d shows the memory location 0x808 reserved for the local variable i. Value of the W15 register is increased to account for the reservation made for the local variables, but the value of the W14 register remains the same.
Fig. 8-4d Stack after entry to subprogram (W15=0x80A, W14=0x808)
How many locations have been occupied? The answer is three. The W14 register is 16-bit wide and occupies only one location, whereas the PC register is 24-bit wide thus it occupies two locations. The 4 bits unused for saving the PC register are used for saving the current priority level. The parameter address is claculated as W14-8. The register W14 points to the memory location next to those where the parameter, PC register, and W14 register are saved. From this value one should subtract 2 because of the W14 register, 4 because of the PC register which occupies two memory locations, and 2 because of the parameter. For this reason from the value saved in the frame register W14 one should subtract 8. The compiler has generated additional code which reserves the place at the top-of-stack occupied by the local variable i. This has not influenced the value of the register W14 but did influence the value of the register W15 because it always has to point to the top-of-stack.
After entering the procedure, the register W15 points at the location 0x80A, the register W14 to location 0x808, and the parameter is at W14-8=0x800. After the procedure is comlpeted, it is not required to check how many data has been put on the stack and how many has been taken off the stack. The register W14 points to the location next to the locations where the important registers have been saved. The value of the register W14 is written into the W15 register, previous value of the register W14 is taken off the stack and immediatley after, so is the value of the PC register. This is done automatically by the hardware. The compiler has added a section of the code to the main program which then calls the procedures on the stack and reads all parameters saved in it in order to recover the value of the W15 register and return the position of the stack to the previous state.
The following example gives a program consisting of one function and a program using this function. The difference between a function and a procedure lies in the result returned to the main program by the function.
program MemoryTest2; var m,n:word; function MyFunc1(a,b:word):Word; var i, j:word; begin i :=a+2; j :=b-1; result:=i+j; end; begin TRISD :=0; m := 3; n := 5; nop; m :=MyFunc1(m, n); LATD := 2; end.
The program starts by executing the instruction TRISD :=0;. After that, it allocates the value 3 to the variable m. Then, it calls the function. What happens at this moment? The compiler has generated a code which push to the stack the variables m and n, Fig. 8-5a, and then jumps to the address in the memory corresponding to the subprogram (function).
Fig. 8-5a Stack before subprogram is entered (W15=0x804, W14=xxxx)
The hardware automatically saves on the stack the PC register to determine, after the subprograme is completed, the address from which the main program resumes the execution, Fig. 8-5b. The PC register is 24 bit wide, two memory locations are required in the data memory to save this register. The lower 16 bits (PCL) are copied first and then the higher (PCH) 8 bits (extended to 16 bits).
Fig. 8-5b Stack after jump to subprogram (W15=0x808, W14=xxxx)
After the PC register is saved, the hardware also copies and saves the W14 register (frame register) and then writes into it the current value of the W15 register, Fig. 8-5c. This saves the information where the last location used by the main progam is. Whatever the subprogram would do with the section of the stack after this address will have no influence on the execution of the main program. Therefore, the W14 register is the boundary between the local variables of the subprogram (function in this case) and the parameters pushed in the stack from the main program. In addition, this allows to find the address where the values of the parameters a and b, valid at the time of calling the function, are. This is done simply by subtracting from W14 the number of locations occupied by the PC and W14 registers at the moment of jumping to the subprogram.
Fig. 8-5c Stack after entry to subprogram (W15=0x80A, W14=0x80A)
Upon jumping to the subprogram (function in this example), the compiler has the task of providing the locations required for the result and the local variables. In this example the result is of the type word and the local variables i and j are of the type word. Fig. 8-5d shows the memory locations 0x80A, 0x80C, and 0x80E reserved for the rersult of the function result and the local variables i and j. Value of the W15 register is increased to account for the reservation made for the result and the local variables, but the value of the W14 register remains the same.
Fig. 8-5d Stack after entry to subprogram (W15=0x80F, W14=0x80A)
Similarly to the call of a procedure, the parameter is in the memory location W14-8, but the parameter i is in W14+2, and the result of the function (result) is written in the location W14.
What would have happened if the locations for the local variables were reserved first and then the location for the result? Fig. 8-5e shows the case of calling the function MyFunc1 from the previous example if the locations for the local variables i and j were reserved first and then the location for the result.
Fig. 8-5e Stack after entry to subprogram when the locations for the local variables i and j were reserved first and then the location for the result (W15=0x80C, W14=0x808)
The local variable i is in the memory location W14, j is in W14+2, and the result is in W14+4. The function could end correctly and return the result to some location. The main program could not read the result because it depends on the number of the locations reserved for the local variables. For each function the main program would have to read differently the result of the function. For this reason by reserving first the memory location for the result ensures that the main program knows the exact location containing the result of the function.
After completion of a subprogram (function), the content of the register W14 is written into the W15 register. This releases the memory locations of the local variables. They are of no importance for further execution of the main program. The location where the result was saved is released together wtih these locations. The process of releasing these locations does not write anything into the memory, therefore the value of the result remains the same. From the stack the previous value of the register W14 is written into the W14 register, and the values saved at the memory locations 0x806 and 0x804 are written into the PC register which points to the next instruction of the main program after the subprogram (function) was called. Only the parameters a and b remain on the stack, as shown in Fig. 8-5f. All this is done by the hardware, i.e. a microcontroller from the dsPIC30F family does that automatically. After this, the floor is taken by the compiler.
Fig. 8-5f Stack after return from subprogram (W15=0x804, W14=xxxx)
After return from the subprogram, on the top-of-stack are the parameters used for calling the function. Since they are no longer required, they are taken off the stack. Now the value of the stack register W15 is 0x800. The result of the function should be read. It is in the location W15+10, i.e. 0x800+0x00A=0x80A. Between the top-of-stack (pointed by the register W15) and the location where the result is saved are the memory locations where the parameters were saved (2 locations, 4 addresses) and the memory locations where the registers PC and W14 were saved (3 locations, 6 addresses). This gives the value 10 by which W15 should be increased to obtain the address of the location containing the result.
The initial value of the register W15 (pointer of the top-of-stack) is 0x800. The value of the W15 register could be changed by writing into it, but this is not recommened due to potential loss of data located on the top-of-stack.
Care should be taken that the stack is in the X space of the data memory. If the memory space is not adequately used, there is a possibility that, as the stack grows, some of the local variables are overwritten by the stack values. The probability of this type of collision increases during the exceution of DSP instructions which use the X space.
Thanks to this algorithm and the memory model, it is possible to nest the procedures and functions or call procedures and functions from the interrupt routines and traps without fear that after the return to the main program some of the registers can be overwritten and their values lost. It should be mentioned that the compiler when calling an interrupt routine, in addition to the already mentioned tasks, generates a code for saving all general purpose registers on the stack which ensures that while writing an interrupt routine one does not have to take into account which register was used, but the interrupt routine can be considered a program independent of the main program in so far as the general purpose registers are concerned.
The parameters and variables which take more than one memory location, e.g. 32-bit integers, are kept in the memory in the following way. The lower 16 bits are saved first (in the lower address) and then the higher 16 bit are saved (in the higher address).
At the end of this section a description is given of the registers used to control modulo and bit-reversed addressing.
| name | ADR | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Reset State |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| MODCON | 0X0046 | XMODEN | YMODEN | - | - | BWM<3:0> | YWM<3:0> | XWM<3:0> | 0x0000 | |||||||||
Table 8-3 MODCON register
XMODEN - X space modulo addressing enable bit YMODEN - Y space modulo addressing enable bit BWM<3:0> - Register select for bit-reversed addressing bits YWM<3:0> - Y space register select for modulo addressing bits XWM<3:0> - X space register select for modulo addressing bits
| name | ADR | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Reset State |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| XMODSRT | 0X0048 | XS<15:1> | 0 | 0x0000 | ||||||||||||||
Table 8-4 XMODSRT register
XS<15:1> - X space modulo addressing start address bits
| name | ADR | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Reset State |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| XMODEND | 0X0050 | XE<15:1> | 1 | 0x0001 | ||||||||||||||
Table 8-5 XMODEND register
XE<15:1> - X space modulo addressing end address bits
| name | ADR | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Reset State |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| YMODSRT | 0X0052 | YS<15:1> | 0 | 0x0000 | ||||||||||||||
Table 8-6 YMODSRT register
YS<15:1> - Y space modulo addressing start address bits
| name | ADR | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Reset State |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| YMODEND | 0X0054 | YE<15:1> | 1 | 0x0001 | ||||||||||||||
Table 8-7 YMODEND register
YE<15:1> - Y space modulo addressing end address bits
| name | ADR | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Reset State |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| XBREV | 0X0056 | BREN | XB<14:1> | 0x0000 | ||||||||||||||
Table 8-8 XBREV register
BREN - Bit-reversed addressing enable bit XB<14:1> - Bit-reversed modifier bits
Usually the programs for the dsPIC devices are written in some of the higher programming languages. However, sometimes it is required that a section of the code is written in the assembler. This need is particularly emphasized for the processes involving complex processing. The optimization is possible in some of the higher programming languages, but it is most efficient when using the assembler, assuming that the architecture of the dsPIC30F devices is known. In these cases the core of the algorithm (most demanding part of the program) is programmed using the assembler. The basic problem when using the assembler instruction set is data memory access, which could be done in several ways. Each of these has its merits and defficiencies. It is thus very important to know the addressing modes and use them correctly while writing a program.
Each assembler instruction can be divided into two parts. The first part of an instruction is the operation which is carried out (like mov, add, etc.) and the second is the operand(s). The operand is a value undergoing the operation. E.g. the instruction DEC W0, W1 carries out the operation of decrementing, defined by DEC, and the operands are the values in the registers W0 and W1.
For the family of dsPIC30F devices there are four addressing modes:
Direct memory addressing is the mode where the operand is at the memory location specified by the instruction. This means that the operation is accompanied by the address in the memory where the value undergoing the operation is located. An example of this mode can be when the task is to take the value located in an addess in the memory and transfer it to a general purpose register (W0...W15).
MOV 0x0900, W0.
This defines the operation of moving MOV (MOVe) while the operands are the value at the adrress 0x0900 in the data memory and the value kept in the register W0. The contents of the data memory at location 0x0900 and the register W0 before and after this instruction are shown in Fig. 8-5.
Fig. 8-5 Contents of the data memory at location 0x0900 and the register W0 before and after the instruction
When using direct memory addressing, there are restrictions depending on the type of the operation used.
The majority of operations can use direct memory addressing only for the lowest 8KB of the memory, while some (like MOV) can use all 64KB. The operation MOV therefore can access any memory location and read the value from it or write in a new value.
The second restriction is that during execution of one instruction only one memory location is accessible (the exception are DSP operations to be discussed later). In an istruction having several operands only one can be addressed by direct memory addressing.
Direct memory addressing supports 8-bit and 16-bit access to the memory. From the memory can be read or in the memory can be written in an 8-bit (byte) or a 16-bit (basic size for the dsPIC30F family) value.
Direct memory addressing is most often used when a value shoud be read from the memory or a result saved in it.
Direct register addresing is the addressing mode where the operand is in the register cited in the instruction. Any general purpose register (W0...W15) can be used.
ADD W0, W1, W2
The consequence of this instruction will be taking the values from the registers W0 and W1, adding them, and saving the result in the register W2. Here there are three operands: the values of W0, W1, and W2. The third operand will be overwritten by the result of adding of the first two. The values of the registers W0, W1, and W2 before and after the instruction are shown in Fig.8-6.
Fig. 8-6 Contents of the registers W0, W1, and W2 before and after the instruction
The advantage of direct register addressing over direct memory addressing is that there are no restrictions in its use. All operations support direct register addressing. For this reason direct register addressing is most often used; it is also very suitable for looping (DOO and LOOP).
Indirect register addressing means that the operand is in the memory location whose address is written in one of the general purpose registers (W0...W15). The value in the register in this case is the pointer to the memory location where the operand is saved. Indirect register addressing is very useful because it allows the value in the register to be changed before or after the operation is carried out (within the same instruction). This allows that the data, saved sequentially in the memory (one after the other), are processed very efficiently.
MOV [W1], W3
The consequence of this instruction is that the value in the memory pointed by the register W1 will be written in the register W2. The register values and memory locations before and after the instruction are shown in Fig. 8-7.
Fig. 8-7 Values of the registers and memory locations before and after the instruction
The flow of operations is the following:
As already mentioned, indirect register addressing allows processing several data, sequentially saved in the memory. This is accomplished by changing automatically the value of the register used for indirect register addressing.
There are four methods of changing the register value in the process of indirect register addressing:
In the pre-increment and pre-decement addressing modes the value of the register is changed (increased or decreased) first and then the operand address is read. In the post-increment and post-decrement addressing modes the value of the register is read first and then changed (increased or decreased). In this way the register values are increased or decreased in two steps. The reason that there are two steps is because 16-bit words are in question. Of course, it is possible to read only one byte, but it should be specified that an 8-bit operation is being executed. Otherwise, 16-bit operation is understood.
MOV $0900, W1 MOV #0, W2 REPEAT #5 MOV W2, [W1++]
This example writes zeros to six sequential locations in the memory. The instruction REPEAT has the consequence that the subsequent operation is executed the specified number of times plus one, i.e. MOV W2, [W1++] will be executed six times.
Attention!!!
All operations are 16-bit, unless specified otherwise. This means that one memory location contains two bytes. Even if the operation was 8-bit, the value of the register would be incremented by two, not by one.
The values of all relevant registers and memory locations before the execution of the above program, before the loop (after the first two instructions) and after the execution of the loop are shown in Fig. 8-8.
| At Start | Before the loop | After the loop |
|---|---|---|
| W1 0xFFFF | W1 0x0900 | W1 0x0900 |
| W2 0xFFFF | W2 0x0000 | W2 0x0000 |
| 0x0900 0xFFFF | 0x0900 0xFFFF | 0x0900 0x0000 |
| 0x0902 0xFFFF | 0x0902 0xFFFF | 0x0902 0x0000 |
| 0x0904 0xFFFF | 0x0904 0xFFFF | 0x0904 0x0000 |
| 0x0906 0xFFFF | 0x0906 0xFFFF | 0x0906 0x0000 |
| 0x0908 0xFFFF | 0x0908 0xFFFF | 0x0908 0x0000 |
| 0x090A 0xFFFF | 0x090A 0xFFFF | 0x090A 0x0000 |
Fig. 8-8 The values of all relevant registers and memory locations before, during and after the execution of the program
There is another mode of indirect register addressing. This is the shift register mode. It is very useful for accessing members of an array.
MOV [W1+W2], W3
The address of the first operand is calculated by adding the values in the registers W1 and W2. The obtained value is the address in the memory where the operand is saved (the value which should be written into the register W3). The location with this address is read and the value written into the register W3. All this is performed in one instruction.
Of all described modes of indirect register addressing (without register modification, with register modification, and shift register), the majority of instructions supports only indirect rgeister addressing without modification. The instruction MOV supports all the modes. DSP instruction set supports only post-increment and post-decrement mode, but the value by which a register is modified can be selected. The increment/decrement value can, in this case, be ±2, ±4, and ±6. For more details concerning DSP instructions, see Chapter 11.
Literal addressing is the addressing mode where the operand is located immediately after the operation. It is not required to read any register or memory location. The operand is carried together with the operation code as a constant to be used during the execution of the instruction.
The size of the constant depends on the operation to be executed. The constant can be signed or unsigned and can be saved with a different number of bits. It is customary to specify the limitations in one of the following ways:
Table 8-9 gives a list of all possible formats of the constant for literal addressing, together with the instructions where the constant is used. These are the assembler instructions which are very seldom used if the programming is performed in a higher level language, but it is of considerable importance to know the limitations in order to use correctly certain instructions of the higher level languages. E.g. shift operand (instructions ASR, LSR, SL) can be done within the range 0-16, which is logical since the 16-bit data are involved.
| Operand | Instruction where it is used | Range |
|---|---|---|
| #bit4 | BCLR, BSET, BTG, BTSC, BTSS, BTST, BTST.C, BTST.Z, BTSTS, BTSTSS.C, BTSTS.Z | 0 ... 15 |
| #lit1 | PWRSAV | 0 ... 1 |
| #lit4 | ASR, LSR, SL | 0 ... 15 |
| #lit5 | ADD, ADDC, AND, CP, CPB, IOR, MUL.SU, MUL.UU, SUB, SUBB, SUBBR, SUBR, XOR | 0 ... 31 |
| #lit8 | MOV.B | 0 ... 255 |
| #lit10 | ADD, ADDC, AND, CP, CPB, IOR, RETLW, SUB, SUBB, XOR | 0 ... 1023 |
| #lit14 | DISI, DO, LNK, REPEAT | 0 ... 16383 |
| #lit16 | MOV | 0 ... 65535 |
| #Slit4 | ADD, LAC, SAC, SAC.R | -8 ... +7 |
| #Slit6 | SFTAC | -32768 .. +32767 |
| #Slit10 | MOV | -512 ... +512 |
Table 8-9 Immediate addressing operands
ADD W1, #4, W2
In the example the value of the register W1 is added 4 and the result is written into W2. For the second operand the literal addressing is used. From the table it can be seen that with the instruction ADD one can use constants within the range 0...31.
NOTE: Individual instructions can use different ranges. E.g. the instruction ADD has three forms for literal addressing. This should be taken care of only while writing a part of the program using the assembler. When using higher programing languages (PASCAL, C, BASIC), the compiler takes care of the form that should be alocated to a given instruction.