Myke's Home Page

Book CD-ROM Home

File Copying/Harddrive Setup

Development Tools

Experiments

Projects

Useful Code Snippets and Macros

Introduction to Electronics

Introduction to Programming

Datasheets

PCBs

Links

McGraw-Hill Professional Publishing

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;
    }