Updated: 00.12.10

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.

These pieces of code can be "cut and pasted" into your applications.

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


1. 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:


 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

              

2. Increments and Decrements

Incrementing a 16 Bit value is very simple:


  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:


  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:


  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

              

3. 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:


  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:


  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:


  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:


  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:


  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:


  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:


  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

              

4. 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:


  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:


  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:


  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 

              

5. 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:


  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.


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

              

for Equals add the lines:


  btfss  STATUS, Z			;  Execute following Code if == 0

   goto  Zero_Skip			;  Else, Code != 0, Skip Over

              

for Not Equals, append:


  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:


  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:


  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:


  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

              

6. 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:


  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).


  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:


  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

              

7. 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.


  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 + 1, w            ;  If Divisor < Dividend then

  subwf  Dividend + 1, w           ;   Don't Take Away

  movwf  Temp

  movf   Divisor, w

  subwf  Dividend, w

  btfss  STATUS, C

   decf  Temp, f

  btfsc  Temp, 7                   ;  If "Temp" Negative then

   goto  Skip                      ;   Divisor < Dividend



  movwf  Dividend                  ;  Save the New Dividend

  movf   Temp, w

  movwf  Dividend + 1



  movf   Count, w             	   ;  Add Count to the Quotient

  addwf  Quotient + 1, f

  movf   Count, 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

              

This division routine is designed to only handle positive numbers - there is no general algorithm that handles both positive and negative numbers and passes back both the quotient and remainder with the correct polarity efficiently.

A general form for a division routine (using the algorithm shown above) could be the division of the core of the pseudo-code is a bit-shift analogous algorithm to multiplication that can handle positive and negative numbers.


  if (Dividend < 0) {		//  Change dividend to positive number

    Dividend = 0 – Dividend;

    dividendneg = 1;		//  Mark we have to change it back

  } else

    dividendneg = 0;

  if (Divisor < 0) { 		//  Repeat with the Divisor

    Divisor = 0 – Divisor;

    divisorneg = 1;

  } else

    divisorneg = 0;



  Count = 0;			//  Going to Count where division starts

  Quotient = 0;			//  Store the Quotient

  while (( Divisor & 0x0400 ) != 0) { 

//  Find the Start of the Division

    Count = Count + 1;		//  Increment the Number of Bits Shifted

    Divisor = Divisor << 1;

  }



  while (Count != 0) { 		//   Now, do the Division

    if (Dividend >= Divisor) {//  A subtract can take place

      Quotient = Quotient + 2 ^ Count;

      Dividend = Dividend – Divisor;

    }

    Count = Count – 1;

    Divisor = Divisor >> 1;

  }



  if (Dividendneg == 1)		//  Now, change the values

    if (Divisorneg == 1) {

      Quotient = Quotient;

      Remainder = 0 – Dividend;

    } else { 

      Quotient = 0 – Quotient;

      Remainder = 0 – Dividend;

  else				//  The Dividend was Positive

    if (Divisorneg == 1) {

      Quotient = 0 – Quotient;

      Remainder = Dividend;

    } else { 

      Quotient = Quotient;

      Remainder = Dividend;

    }