====== 6502 Math ======
The [[6502]] processor is limited to very simple math operations, and various [[spo600:6502#registers|processor flags]] affect these operations.
==== Decimal Mode ====
The 6502 can perform math in binary or decimal mode.
In binary mode, operations are performed on a single 8-bit value. Numbers may be treated as signed or unsigned (the math is the same).
In decimal mode, the each byte is treated as two decimal digits - the lower 4 bits represent the lower digit, and the upper 4 bits represent the upper digit. Numbers are counted as positive, and digit values greater than 9 are invalid.
Decimal mode is selected by setting the D (Decimal) flag in the [[6502#Registers|Status Register]] using the ''SED'' instruction, and binary mode is selected by clearing the D flag using the ''CLD'' instruction.
The rest of this page deals with binary mode (decimal mode operates in mostly the same way). Note that decimal mode is only applicable to ''ADC'' and ''SBC'' instructions, and may not set some processor flags in an expected way.
==== Addition ====
The ADC (add with carry) instruction adds together:
the value in the accumulator + the specified byte + the carry flag
If the unsigned value overflows a single byte, the carry flag will be set.
It is therefore important to clear the carry flag (with ''CLC'') before adding the lowest byte of a single or multi-byte value. If a multi-byte addition is performed starting with the lowest byte and proceeding to the highest byte, the carry flag will correctly carry bits forward from one byte to the next. For example, this code adds $30F0 and $0120:
LDA #$F0 ; A=$F0
CLC ; C=0
ADC #$20 ; Result is $F0+$20+C = $110; therefore A=$10 and C=1
STA LOWBYTE
LDA #$30 ; A=$30
ADC #$01 ; Value is $30+$01+C = $32; therefore A=$32 and C=0
STA HIGHBYTE
The Overflow (V) flag is set if the signed result of the operation is above 127 ($7f) or below -128 ($80).
==== Subtraction ====
The SBC (subtract with carry) operation subtracts:
the value in the accumulator - the specified byte - (not Carry)
Where the ''not'' operation inverts the value of the carry flag.
The carry flag (which can be viewed as a "borrow" flag for subtraction) is cleared if the result underflows $00 and set otherwise (in other words, if doing unsigned math, the Carry flag is cleared if a bit must be "borrowed").
Normally, you will set the carry flag (with ''SEC'') before performing a subtraction on the lowest byte of a single- or multi-byte subtraction, and then perform subtraction on each byte in sequence up to the highest byte. The borrows will automatically be carried from one byte to the next.
The Overflow (V) flag is set if the signed result of the operation is above 127 ($7f) or below -128 ($80).
==== Division / LSR ====
There is no general division operation available. However, Logical Shift Right will effectively perform a division-by-two. A zero will be rotated into the highest bit, and the lowest bit will be shifted into the Carry flag (C).
==== Multiplication / ASL ====
There is no general multiplication operation available. However, Arithmetic Shift Left will effectively perform a multiplication-by-two. A zero will be rotated into the lowest bit, and highest bit will be shifted into the Carry flag (C).
==== Rotate Right/Left ====
The Rotate Right and Rotate Left (ROR/ROL) instructions are like the LSR/ASL instructions, except that the Carry flag is rotated into one end of the byte, and the bit from the other end is rotated into the Carry flag. For example, ROR will shift C into the high bit and the low bit into C. Therefore, it is possible to perform a multi-byte rotate by stringing together ASL or LSR instructions with ROR or ROL instructions.
For example, to perform a 16-bit right shift:
1. Perform LSR on the high byte. The lowest bit will be placed in the carry flag.
2. Perform ROR on the low byte. The carry flag, containing the lowest bit from the high byte, will be shifted in to the highest bit of the low byte.
The original version of the 6502 had a bug in the ROR instruction, so it was removed from the initial documentation. The chip was revised within the first few months and all later versions of the 6502 implement the ROR instruction correctly.
==== Bitwise Operations ====
The 6502 offers a basic set of [[Bitwise Operations]], including:
EOR Exclusive-OR (sometimes written XOR in other languages)
ORA OR (Accumulator)
AND AND
A NOT operation can be performed using EOR with an immediate value of #$FF, and this can be combined with ORA and AND instructions to build NOR and NAND operations.
In addition, the BIT instruction performs a bitwise AND, sets the Z flag based on the result, and transfers bits 6 and 7 of the operand into the N and V flags.
==== Multiplication and Division ====
There are no multiplication or division instructions on the 6502; it is up to the programmer to provide the appropriate logic. Typical implementations use a combination of shifts and adds; for example, to multiply by 12, the original value shifted left three times (x8) can be added to the original value shifted left two times (x4).
Here is a simple example subroutine which multiplies two 8-bit values, producing a 16-bit product as the result (this code can be tested on the [[6502 Emulator]]):
; multiply_8x8bit.6502
;
; Example of 8-bit x 8-bit multiply with
; 16-bit result on a 6502
;
; This code uses shift-and-add
;
; Chris Tyler 2025-01-23 for the SPO600 course
;
; Copyright (C)2025 Seneca Polytechnic
; Licensed under the terms of the GPLv2+
; See the file LICENSE for details
; ######### MACRO DEFINITIONS
; === ZERO-PAGE MEMORY LOCATIONS
; INPUT PARAMETERS AND RESULT
define M1 $10 ; MULTIPLICAND 1
define M2 $11 ; MULTIPLICAND 2
define RESULT $12 ; RESULT
define RESULT_L $12
define RESULT_H $13
; TEMPORARY COPIES OF M1, M2
define T1 $20 ; 8 BIT VALUE
define T2 $21 ; 16 BIT (FOR SHIFTING)
define T2_L $21
define T2_H $22
; ######### TEST CODE
; NOTE: THE NEXT TWO OPERANDS ARE IN DECIMAL!
; THIS IS DUE TO THE LACK OF $
LDA #7 ; FIRST MULTIPLICAND
STA M1
LDA #9 ; SECOND MULTIPLICAND
STA M2
JSR MULTIPLY ; MULTIPLY THE VALUES
BRK ; SEE 16 BIT RESULT AT 'RESULT'
; ######### MULTIPLY SUBROUTINE
; MULTIPLY THE CONTENTS OF M1 BY THE CONTENTS
; OF M2 AND STORE THE RESULT IN 'RESULT'
;
; Operation:
; 0. Multiplicand M1 is copied to temporary
; variable T1. Multiplicand M2 is copied to
; the 16-bit temporary variable T2. The
; RESULT is zeroed out.
; 1. If either multiplicand is zero, the
; product is zero, so the routine returns
; immediately with RESULT=0.
; 2. T1 is shifted right. If the bit shifted
; out (formerly bit 0) is a '1', then the
; value of T2 is added to the RESULT.
; 4. If T1 is zero after the shift, the
; multiplication is complete and the routine
; returns.
; 5. T2 is rotated left (multiplied by 2).
; 6. Processing continues at step 2.
MULTIPLY:
LDA #$00 ; ZERO OUT THE RESULT AND Tn_H
STA RESULT_L
STA RESULT_H
STA T2_H
LDA M1 ; COPY M1 TO T1
BEQ MULT_DONE ; RESULT=0 IF M1==0
STA T1
LDA M2 ; COPY M2 TO T2
BEQ MULT_DONE ; RESULT=0 IF M2==0
STA T2_L
JMP MULT_FIRST
MULT_NEXT:
ASL T2_L ; SHIFT T2 LEFT
ROL T2_H
MULT_FIRST:
LSR T1 ; SHIFT T1 RIGHT
BEQ MULT_LAST ; DO LAST BIT IF T1==0
BCC MULT_NEXT ; IF BIT0 WAS 0, DO NEXT
LDA T2_L ; IF BIT0 WAS 1:
CLC ; T2 + RESULT-> RESULT
ADC RESULT_L
STA RESULT_L
LDA T2_H
ADC RESULT_H
STA RESULT_H
JMP MULT_NEXT
MULT_LAST: ; CODE IS REDUNDANT TO THE
LDA T2_L ; PRECEEDING BLOCK, BUT
CLC ; WE'VE AVOIDED A SECOND
ADC RESULT_L ; COMPARISON OF T1 WITH #0
STA RESULT_L ; THEREBY SAVING 6-7 CYCLES
LDA T2_H ; PER LOOP ITERATION OR
ADC RESULT_H ; 6-56 CYCLES TOTAL, AT A
STA RESULT_H ; COST OF 13 ADDITIONAL BYTES
MULT_DONE:
RTS
This code is available in the [[spo600:6502_emulator_example_code#additional_examples|example repository]].