Understanding 6502 assembly on the Commodore 64 - (26) Intro to unsigned mathematics

Its time to talk about math.  Mathematics is naturally an important part to programming and can seem very confusing in machine language.  We have gotten comfortable with computers languages that allow for the easy computation of complex formulas.  It is quickly understood that mathematical routines may take many steps to complete a simple operation.






     Before discussing math itself, we must talk about realistic constraints.  Consider that from zero, in either direction there can be an infinite number of values. Since we don’t have an infinite number of memory or processor cycles, when we consider an equation, we should limit the scope to what we are doing.  Larger values will require reserving more memory, so you need to plan ahead.
  
For instance: (Colored for clarity)

     1 byte (8 BIT) values:   00000000 TO 11111111    (00 TO FF)

Constants

7      -  Days in a week 
24    -  Hours in a day 
12    -  Months in a year 
60    -  Seconds in a minute
255  -  Maximum number addressed by 8 bits Values
0      - Degrees Celsius, Freezing point of water at 1 atmosphere
195  - Pounds, my ideal body weight
212   - Degrees Fahrenheit, boiling water at 1 atmosphere


     2 byte (16 BIT) values: 0000000000000000 TO 1111111111111111 (0000 TO FFFF)

Constants

365     - Days in a year
5280   - Feet in a mile
1024   - Megabytes in a gigabyte
1000   - Kilograms in a metric ton
65535 - Maximum number addresses by 16 bits


Variables

4057    - Miles driven on my shovelhead since the engine rebuild
4203    - RPM on a given engine
25000  - A current score on a fictitious video game
54637  - Invoice number
2050    - How many calories I consumed yesterday
50500  - Current amount of bytes transmitted over a serial port 


     As you can see, if we were storing the current day of the week and wrapping around after the 7th day we would not need more than one byte to store our value, while if we were dong the same thing with the day of the year, we would never need more than 2 bytes as 365 exceeds an 8 bit value.

     Constants are naturally easier to work with than variables, because they don't change.  If they changed, they wouldn't be a constant.  This does not negate the need for working with constants in math however.  For instance, 2 years can be 2 * Days in a year, in which a constant is used to produce a variable output.


     Before we look at how to do this in assembly let us look at this real life addition example in decimal. This is the first and last time decimal will be mentioned:

    00007     (just a starting number)
+  00001     (add 1)
=  00008

    00008
+  00001     (add 1)
=  00009

    00009
+  00001     (add 1)
=  00000     (9 goes back to zero with a carry in the next digit)
=  00010     (tens value is incremented by 1)


     As we know in the decimal system, once we add 1 to 9, we wrap around back to zero and add a 1 to the tens value.  Binary works the same way on the base 2 system.  Lets look at the same thing in binary

    00000     (just a starting number 0)
+  00001     (add 1)
=  00001     (Equal 1, no carry)

    00001     (1)
+  00001     (add 1)
=  00000     (Equal 0 plus a carry to the next digit)
=  00010     (2's value is incremented by 1, = 2)

    00010     (2)
+  00001     (add 1)
=  00011     (Equal 3, no carry)

    00011     (3)
+  00001     (add 1)
=  00000     (Equal 0 plus a carry to the next digit)
=  00100     (4's value is incremented by 1, = 4)


    Hexadecimal, as we know, will wrap around on the 16th number, like decimal does at 10, or binary does at 1:

    00x09     (just a starting number 9, is decimal value 9)
+  00x01     (add 1)
=  00x0A     (Equal 0A, no carry, is decimal value 10)

+  00x05     (add 5)
=  00x0F     (Equal 0F, no carry, is decimal value 15)

    00x0F     (is decimal value 15)
+  00x01     (add 1)
=  00x00     (Equal 00, plus a carry to the next digit)
=  00x10     (is decimal value 16)


     For our purposes, with regard to binary and hexadecimal, the 6502 will conduct all of our carrying  within the confines of an 8 bit value.   Once we leave our 8 bit value (0 to 255 / 00 to FF) another process will be needed.



Incrementing and Decrementing

     This is where we begin the most basic of 6502 math, Incrementing and Decrementing.  This is accomplished with the following OPCODES.

INX    - Increments the value in X by 1  
DEX   - Decrements the value in X by 1
INY    - Increments the value in Y by 1
DEY   - Decrements the value in Y by 1 
INC    - Increments the value in a memory address by 1
DEC   - Decrements the value in a memory address by 1


Consider the limitations imposed upon these opcodes:  

     There is no Carry flag effected by these OPCODES, just Z and N
     You can only increment or decrement by a value of 1
     May be done from X or Y or a memory address, Not A


Even though there is no Carry flag effected, there is a Zero flag which is effected, and the Negative flag (not discussed here) which can be useful for a carry on a value that wraps around the maximum 8 bit value of FF.  Since the first four listed opcodes are the same, only acting differently on X or Y, I will demonstrate INX and DEX.

LDX #$03      (A value of 03 in X : Z flag is OFF)
INX                (Now a value of 04 in X : Z flag is OFF)
INX                (Now a value of 05 in X : Z flag is OFF)

and.........

LDX #$03       (A value of 03 in X : Z flag is OFF)
DEX                (Now a value of 02 in X : Z flag is OFF)
DEX                (Now a value of 01 in X : Z flag is OFF)

if we went further......

DEX                (Now a value of 00 in X : Z flag is ON)

Understandably so, as we have learned, if the last instruction produces a zero value, the Z flag will be turned on,  I'm just stating this point now, we will work with this flag later

lets continue.......

DEX                (Now a value of FF in X : Z flag is OFF)


After hitting zero, the value will simply wrap around, and do so indefinately within its 8 bit confines.

DEX                (Now a value of FE in X : Z flag is OFF)


Same thing in the incrementing direction.....

INX                (Now a value of FF in X : Z flag is OFF)
INX                (Now a value of 00 in X : Z flag is ON)
INX                (Now a value of 01 in X : Z flag is OFF)
INX                (Now a value of 02 in X : Z flag is OFF)



Lets take a look at INC and DEC.   These Opcodes will require a memory address after it so it knows which memory location to work on.   This is almost exactly the same as the previously mentioned opcodes.  This can be used in a 16bit memory address, an 8bit zero page address, or either of the two offset by X.

Example:
     DEC $0C00        (16 bit)
     DEC $FB            (8 bit zero page)
     DEC $0C00,X    (16 bit offset by X)
     DEC $FB,X        (8 bit zero page offset by X)


LDA #$02    (A value of 02 in A : Z flag is OFF)
STA  $0C00 (The value of 02 stored on $0C00 : Z flag is OFF)
DEC  $0C00 (The value of 01 stored on $0C00 : Z flag is OFF)
DEC  $0C00 (The value of 00 stored on $0C00 : Z flag is ON)

lets keep going......

DEC  $0C00 (The value of FF stored on $0C00 : Z flag is OFF)
DEC  $0C00 (The value of FE stored on $0C00 : Z flag is OFF)

and in the other direction.....

INC  $0C00 (The value of FF stored on $0C00 : Z flag is OFF)
INC  $0C00 (The value of 00 stored on $0C00 : Z flag is ON)
INC  $0C00 (The value of 01 stored on $0C00 : Z flag is OFF)
INC  $0C00 (The value of 02 stored on $0C00 : Z flag is OFF)


In summary all of these opcodes work just like an odometer on a car that keeps circling back to zero after reaching its maximum value.  Each time it rolls over zero a light turns on which lets you know that the rollover just occurred, and shuts back off after leaving the zero value.






In the next chapter we will work with a basic algorithm that allows values greater than FF to be recorded in memory using the OPCODES we've learned.



Table of contents

No comments:

Post a Comment