Full Version : 16-Bit Arithemtic (PIC ASM)
avr >>PIC 8051 ZILOG ARM TI H8 ETC >>16-Bit Arithemtic (PIC ASM)


Admin3- 04-18-2006
16 Bit Arithmetic

I find that in most of my applications, I have to use 16 bit numbers. I use the code snippets below for the various required functions.

1. Defining 16 Bit Numbers 5. Comparisons with 16 Bit Variables
2. Increments and Decrements 6. Multiplication
3. Addition/Subtraction 7. Division
4. Other Operations on Constants and Variables

Defining 16 Bit Numbers

I define 16 bit numbers in a manner similar to that of an 8-bit number and just give them to bytes in the RAM Register Space. The example below shows how to define an eight bit Variable, followed by two 16 bit Variables:

CODE
CBLOCK 0x020                ;  Start of RAM for the PIC16F877
Reg_8                        ;  Define the 8 Bit Register
Reg_16:2                     ;  Define the first 16 Bit Register
Reg2_16:2                    ;  Define the 2nd 16 Bit Register
ENDC



Incrementing a 16 Bit value is very simple:

CODE
 incf    Reg, f             ;  Increment the Low byte
 btfsc   STATUS, Z          ;  Do we have Zero (Multiple of 256)?
  incf   Reg + 1, f         ;  Increment High byte (if necessary)


If a PIC17Cxx or a PIC18Cxx is used, the "infsnz" instruction to simplify the sixteen bit increment by one instruction:

CODE
 infsnz   Reg, f            ;  Increment "Reg's" Low Byte and Skip
  incf    Reg + 1, f        ;   High Byte Increment if Result is Not
;   Equal to Zero


The Decrement of a 16 Bit value isn't quite so simple:

CODE
 movf    Reg, f             ;  Set "Z" if LOW "Reg" == 0
 btfsc   STATUS, Z
  decf   Reg + 1, f         ;  If Low byte is Zero, Decrement High
 decf    Reg, f



--------------------------------------------------------------------------------

Addition/Subtraction

Addition and Subtraction of 16 Bit Variables to Constants follows the same format with the most significant byte processed first. Addition follows the format:

CODE
 movlw   HIGH 0x01234       ;  Add the high byte first
 addwf   Reg + 1, f
 movlw   LOW 0x01234        ;  Add the Low Byte Next
 addwf   Reg, f
 btfsc   STATUS, C          ;  Don't Inc high byte if carry Reset
  incf   Reg + 1, f


For the PIC17Cxx and PIC18Cxx, the "addwfc" instructions can be used to simplify the operations and eliminate the need for checking the status of the carry flag. For the addition above, the PIC17Cxx or PIC18Cxx code would be:
CODE

 movlw   LOW 0x01234        ;  Add Low Byte First
 addwf   Reg, f
 movlw   HIGH 0x01234       ;  Add High Byte Next
 addwfc  Reg + 1, f

The corresponding subtraction, ie:

Reg = Reg - 0x01234

Looks like:
CODE

 movlw   HIGH 0x01234       ;  Subtract the High Byte First
 subwf   Reg + 1, f
 movlw   LOW 0x01234        ;  Subtract the Low Byte Next
 subwf   Reg, f
 btfss   STATUS, C          ;  Don't Dec high byte if carry Set
  decf   Reg + 1, f  

Again, the enhanced instructions in the PIC17Cxx and PIC18Cxx can used for the sixteen bit subtraction operation. Using the "subwfb" instruction, the sixteen bit subtraction can be simplified to:
CODE

 movlw   LOW 0x01234        ;  Subtract the Low Byte First
 bsf     STATUS, C          ;  Don't pass any "Borrow"
 subwfb  Reg, f             ;  Reg = Reg – w - !C
 movlw   HIGH 0x01234
 subwfb  Reg + 1, f         ;  Reg + 1 = Reg + 1 – w - !C

The general case 16 Bit addition,

Destination = Source + 0x05678

Will look like:
CODE

 movlw   HIGH 0x05678       ;  Add High Byte First
 addwf   Source + 1, w
 movwf   Destination + 1, f ;  Store Result in Destination
 movlw   LOW 0x05678        ;  Add Low Byte Next
 addwf   Source, w
 movwf   Destination, f     ;  Store Result
 btfsc   STATUS, C          ;  Is the Carry Flag Set?
  incf   Destination + 1, f ;   Yes, Increment High Byte

If the Destination is different from both values to be added, ie:

c = a + b

the code is changed to save the sums in "w" and then store it in "c" like:
CODE

 movf   a + 1, w            ;  Add the High Bytes
 addwf  b + 1, w
 movwf  c + 1
 movf   a, w                ;  Add the Low Bytes
 addwf  b, w
 movwf  c
 btfsc  STATUS, C           ;  Increment due to Carry
  incf  c + 1

Subtraction is carried out in the same way, but care must be taken to ensure that the subtracting Register is kept straight (something that is less of an issue with addition). If you want to do the following statement:

c = a - b

You would use the code:
CODE

 movf   b + 1, w            ;  Get Value to be subtracted
 subwf  a + 1, w            ;  Do the High Byte
 movwf  c + 1
 movf   b, w                ;  Get the Value to be Subbed
 subwf  a, w
 movwf  c
 btfss  STATUS, C           ;  Look for the Carry
  decf  c + 1


--------------------------------------------------------------------------------

Other Operations on Constants and Variables

Doing other operations (bitwise or whatever) on 16 bit values can use the code shown above as a base.


For example, ANDing a 16 Bit Variable with 0x0A5A5 would be done like:
CODE

 movlw  0x0A5               ;  Get Value for ANDING
 andwf  Reg + 1, f          ;  Do the High Byte
 andwf  Reg, f              ;  Do the Low Byte

There is one difference, however and that is to do with rotating 16 Bit values. Rotating must be carried out in such a way that the carry flag is always correct for the shift.


To shift left:
CODE

 bcf    STATUS, C           ;  Clear the Carry Flag for new bit
 rlf    Reg, f              ;  Shift the Low Byte
 rlf    Reg + 1, f          ;  Shift High Byte with Low Carry

and to shift right:
CODE

 bcf    STATUS, C           ;  Clear Carry Flag for the New bit
 rrf    Reg + 1, f          ;  Shift down the High Byte
 rrf    Reg, f              ;  Shift Low Byte with Valid Carry


--------------------------------------------------------------------------------

Comparisons with 16 Bit Variables

Comparisons involving 16 Bit Variables require that the comparison value (or Register) is subtracted from the Register to be checked. The results of this will then tell you what is going on with the condition. I use the same code as is shown above and save the result in temporary values and then look at the result. The subtraction code used for comparing a 16 Bit Variable to another 16 Bit variable is:
CODE

 movf   Reg2 + 1, w         ;  Get the High Byte of the Result
 subwf  Reg1 + 1, w
 movwf  _2                  ;  Store in a Temporary Register
 movf   Reg2, w             ;  Get the Low Byte
 subwf  Reg1, w
 btfss  STATUS, C           ;  Decrement High if Necessary
  decf  _2

At the end of this series of instructions, "w" contains Reg2 - Reg1 and "_2" contains Reg2HI - Reg1HI with the borrow result of Reg2 - Reg1.


If the Variable is to be compared against an immediate value, then the "movf" Instructions would be replaced with "movlw" and the two bytes of the immediate value.


There are six basic conditions that you can look for: Equals, Not Equals, Greater Than, Greater Than or Equal, Less Than, Less Than or Equals. So, to discover whether or not I have any of these conditions, I add the following code:


For Equals and not equals, the value in "w" is ORed with "_2" to see if the Result is equal to zero.
CODE

 iorwf  _2, w               ;  Is the Result == 0?

for Equals add the lines:
CODE

 btfss  STATUS, Z           ;  Execute following Code if == 0
  goto  Zero_Skip           ;  Else, Code != 0, Skip Over

for Not Equals, append:
CODE

 btfsc  STATUS, Z           ;  Execute following if != 0
  goto  NotZero_Skip        ;  Else, Code == 0, Skip Over

If a Greater than (The 16 Bit Variable is Greater than the Comparison Value), then the result will not be less than Zero. Actually, the same code (just with a different Bit Skip) can be used to test.


For Greater Than:
CODE

 btfsc  _2, 7               ;  Not Negative, 16 Bit is Greater
  goto  NotGreater_Skip     ;  Else, Skip if Not Greater than
 iorwf  _2, w               ;  Is it Equal to Zero?
 btfsc  STATUS, z           ;  No, It is Greater than
  Goto  NotGreater_Skip     ;  Else, if Zero, Not Greater than

Note that just the most significant bit of the 16 but difference is checked. If this bit is set (= 1), then the 16 Bit Variable is less than the Comparison. If it is reset (= 0), then it is greater than and you should check to see if the result is not equal to zero (or else it is equal).


For Less Than:
CODE

 btfss  _2, 7               ;  Negative, 16 Bit is Less Than
  goto  NotLess_Skip        ;  Else, Skip because Not Less Than

To check for Greater or Equal To, the last three lines of the code checking for Greater Than are simply erased. To check for Less or Equal To, the three lines from Not Equal to are added before the check for less than.


Here is the complete code for compare and skip on Reg1 less than or equal to Reg2:

CODE


 movf   Reg2 + 1, w         ;  Get the High Byte of the Result
 subwf  Reg1 + 1, w
 movwf  _2                  ;  Store in a Temporary Register
 movf   Reg2, w             ;  Get the Low Byte
 subwf  Reg1, w
 btfss  STATUS, C           ;  Decrement High if Necessary
  decf  _2
 iorwf  _2, w               ;  Check for Equal to Zero
 btfsc  STATUS, Z           ;  If Not Zero, Jump Over
  goto  EqualLess_Skip      ;  Equals, Jump to the Code
 btfsc  _2, 7               ;  If Number is Negative, execute
  goto  EqualLess_Skip      ;  Else, Jump Over


--------------------------------------------------------------------------------

Multiplication

For both multiplication and division, repeated addition could be used, but I find that using a scaling routine works much better and faster. These algorithms test bits and only operate if it is appropriate.


Here is a Sixteen Bit Multiplication with a Sixteen Bit Result:

CODE


 clrf   Product
 clrf   Product + 1

 movlw  16        ;  Operating on 16 Bits
 movwf  BitCount

Loop                         ;  Loop Here for Each Bit

 rrf    Multiplier + 1, f   ;  Shift the Multiplier down
 rrf    Multiplier, f       ;   by one

 btfss  STATUS, C           ;  If the bit is set, add
  goto  Skip                ;   the Multiplicand to the
                            ;   "Product"
 movf   Multiplicand + 1, w
 addwf  Product + 1, f
 movf   Multiplicand, w
 addwf  Product, f
 btfsc  STATUS, C
  incf  Product + 1, f

Skip                         ;  Shift up Multiplicand and
 bcf    STATUS, C           ;   Loop Around
 rlf  Multiplicand, f
 rlf    Multiplicand + 1, f

 decfsz BitCount
  goto  Loop


The code given below is the most efficient way of doing a sixteen bit multiply with a 32 bit result. It is not immediately obvious, but it's very clever. Rather than use a 32 bit add each time the shifted data is detected, it provides a sixteen bit (with valid carry) add and then shifts the data down. This Code does not change "Multiplicand", but does change "Multiplier". Note that in the code, I use a thirty two bit value for "Product" (using a "Product:4" line in the "CBLOCK" variable declare statement).

CODE

 clrf   Product + 2         ;  "Product" will be the
 clrf   Product + 3         ;   Result of the Operation

 movlw  16                  ;  Operating on 16 Bits
 movwf  BitCount

Loop                         ;  Loop Here for Each Bit

 rrf    Multiplier + 1, f   ;  Shift the Multiplier down
 rrf    Multiplier, f       ;   by one

 btfss  STATUS, C           ;  If the bit is set, add
  goto  Skip                ;   the Multiplicand to the
                            ;   "Product"
 clrf   Product + 4
 movf   Multiplicand + 1, w
 addwf  Product + 3, f
 btfsc  STATUS, C           ;  Make Sure the Carry is Passed
  incf  Product + 4, f      ;   to the Next Byte
 movf   Multiplicand, w
 addwf  Product + 2, f
 btfsc  STATUS, C
  incfsz Product + 3, f     ;  Make Sure Carry is Passed with
   goto $ + 2               ;   the Shift
   incf Product + 4, f

Skip                         ;  Shift "Product" Down with
 bcf    STATUS, C
 rrf    Product + 4, f
 rrf    Product + 3, f      ;   the Reset Carry from the
 rrf    Product + 2, f      ;   Multiplier shift down or
 rrf    Product + 1, f      ;   the result of the sixteen
 rrf    Product, f          ;   bit addition.

 decfsz BitCount
  goto  Loop


For the PICmicros that have built in eight by eight multipliers, the code for sixteen bit multiplication uses the techniques taught in high school mathematics for multiplying together to variable factors. Instead of thinking of a sixteen bit number as just a contiguous set of sixteen bits, the number is broken up as two sets of numbers that are eight bits in size. The PIC18CXXX sixteen bit multiplication with a thirty two bit result is:

CODE

 clrf    Product + 2        ;  Clear the High-Order Bits
 clrf    Product + 3
 movf    Al, w              ;  Do the "L" Multiplication first
 mulwf   Bl
 movf    PRODL, w           ;  Save result
 movwf   Product
 movf    PRODH, w
 movwf   Product + 1
 movf    Al, w              ;  Do the "I" Multiplication
 mulwf   Bh
 movf    PRODL, w           ;  Save the Most Significant Byte First
 addwf   Product + 1, f
 movf    PRODH, w
 addwfc  Product + 2, f     ;  Add to the Last Result
 movf    Ah, w              ;  Do the "O" Multiplication
 mulwf   Bl
 movf    PRODL, w           ;  Add the Lower Byte Next
 addwf   Product + 1, f
 movf    PRODH, w           ;  Add the High Byte First
 addwfc  Product + 2, f
 btfsc   STATUS, C          ;  Add the Carry
  incf   Product + 3, f
 movf    Ah, w              ;  Do the "F" Multiplication
 mulwf   Bh
 movf    PORDL, w
 addwf   Product + 2, f
 movf    PRODH, w
 addwfc  Product + 3, f


--------------------------------------------------------------------------------

Division

The division routine provided here first finds how far the divisor can be shifted up before comparing to the quotient. The "Count" variable in this routine is a 16 Bit variable that is used to both count the bits as well as add to the quotient. "Temp" is an 8 Bit temporary Storage Variable. At the end of the division routine, "Dividend" will contain the remainder of the operation.


As was pointed out on the PICList, the original 16 Bit division routine only really produced random numbers. This code is the corrected version of it and should work without any problems.

CODE


 clrf   Quotient
 clrf   Quotient + 1

 movlw  1                   ;  Initialize Count
 movwf  Count
 clrf   Count + 1

StartLoop                    ;  Find How Large "Divisor" can
                            ;   be
 btfsc  Divisor + 1, 7      ;  If at the "top", then do
  goto  Loop                ;   the Division

 bcf    STATUS, C           ;  Shift Count and Divisor Up
 rlf    Count, f
 rlf    Count + 1, f
 
 rlf    Divisor, f
 rlf    Divisor + 1, f

 goto   StartLoop

Loop                         ;  Now, Take Away "Divisor"
                            ;   from "Dividend"
 movf   Divisor, w          ;  If Dividend => Divisor then
 subwf  Dividend, w         ;   Take Away
 movwf  Temp
 movlw  0
 btfss  STATUS, C
  movlw 1
 addwf  Divisor + 1, w
 subwf  Dividend + 1, w
 btfss  STATUS, C
  goto  Skip                ;   Divisor < Dividend

 movwf  Dividend + 1        ;  Save the New Dividend
 movf   Temp, w
 movwf  Dividend

 movf   Count, w            ;  Add Count to the Quotient
 addwf  Quotient, f
 movf   Count + 1, w
 addwf  Quotient + 1, f     ;  No Opportunity for Carry

Skip                         ;  Shift Divisor/Count Down

 bcf    STATUS, C
 rrf    Divisor + 1, f
 rrf    Divisor, f

 rrf    Count + 1, f        ;  If Carry Set after Count
 rrf    Count, f            ;   Shift, Finished

 btfss  STATUS, C           ;  If Carry NOT Set, then
  goto  Loop                ;   Process next Bit
 



Forumer™ is Voted #1 Free Forum Hosting provider
Build your own community today with the largest message board hosting company.