====================================================================
DR 6502 AER 201S Engineering Design 6502 Execution Simulator
====================================================================
Supplementary Notes By: M.J.Malone
Advanced 6502 Assembly Code Examples
====================================
The remainder of this file will be in a format acceptable to
TASM for direct assembly. Note there may be errors in the code, it
is not intended that it be cut up and included in students files.
It is meant only as an example of addressing modes and
instructions. The first example is a prime number finder. The
second example is a set of subroutines to maintain a multitasking
system.
;==============================================================================
; Advanced Coding Examples for the Students of AER201S
;==============================================================================
;
;
.ORG $E000
SEI ; INITIALIZING THE STACK POINTER
LDX #$FF
TXS
;
LDX #$00
LDY #$00
Delay DEX
BNE Delay
DEY
BNE Delay
;
;=============================================================================
; Prime Number Finder
;=============================================================================
; This Prime Number Finder uses the sieve method to find the primes up to 255
; and then uses those primes to find the primes up to 65535. Note that this
; is of course not THE most efficient way to find primes but it makes a good
; demonstration.
; It would be neat to stack this code up against a casually written/optimized
; compiled C prime number finder on a raging 386. I have a feeling there will
; be less than a factor of ten difference on execution speed. You may be
; surprised just how fast the 6502 is on simple problems.
;
Test_num = $00 ; Test Number to Eliminate non-primes
Array = $00 ; Base Address for the array of primes
;
;
lda #$01
sta $a003
lda #$01
sta $a001 ; Turns on an LED on bit zero of port A of VIA 1
; to let you know it has started looking for primes
;
page 2
ldx #$01 ; Initialize the array of numbers
Init_Loop txa
sta Array,x
inx
bne Init_loop
;
lda #$02 ; Initialize the Test_num = 2
sta Test_num
lda #$04 ; Put the square of 2 in the accumulator
; as the first non-prime
;
; Start Setting the Multiples of the Test_num to zero
Start_num
Got_Mult tax
stz Array,x ; Set multiples of Test_num to zero since they
clc ; are not prime.
adc Test_num ; Calculate the next multiple
bcs Next_num ; Until the Multiples are outside the array
jmp Got_Mult
;
Next_num inc Test_num ; Go on to the next Test_num
ldx Test_num
cpx #$10 ; Until Test_num => sqrt(largest number)
beq More_Primes
lda Array,x
beq Next_num ; Don't use Test_num if Test_num is not prime
txa
; Got a valid new Test_num, now find its square because all non-primes
; multiples less than its square are eliminated already
dex
clc
Square adc Test_num
dex
bne Square
; OK Got the square of Test_num in the accumulator
; lets start checking
jmp Start_num
;
;
More_Primes
;
; Ok now we have all the primes up to 255 in the memory locations $01-$FF
; Lets repack them more neatly into an array with no spaces to make our
; life easier
;
ldx #$00 ; .X is a pointer into the loose array
ldy #$01 ; .Y is a pointer into the packed array
Repack inx
beq Done_packing
lda Array,x
beq Repack
sta Array,y
iny
jmp Repack
;
page 3
Prime_Ptr = $F0 ; This is a points into the list of primes greater
; than $FF and less that $10000
;
Poss_Prime = $F2 ; Possible prime
Temp = $F4 ; A Temporary Number used to find modulus
Shift = $F6 ; Number of Places that .A is shifted
TempArg = $F7 ; A temporary number; argument of modulus
;
Done_packing
lda #$00 ; Store a $00 at the end of the array of short
sta Array,y ; primes so we know when we have reached the end
lda #$00
sta Prime_ptr ; Set the Prime Pointer (for primes >$FF)
lda #$02 ; pointing into $0200. The found primes will be
sta Prime_ptr+1 ; recorded sequentially from there on.
;
lda #$01 ; Start with $0101 as the first possible prime
sta Poss_Prime
sta Poss_Prime+1
;
Next_PP ldy #$02
Next_AP lda Array,y
beq Prime
jsr Mod
beq Next_Poss_prime ; it was a multiple of Array,y
; and therefore not prime
iny
jmp Next_AP
;
Prime ldx #$00
lda Poss_prime ; Store prime away in the array of primes
sta (Prime_ptr,x)
inx
lda Poss_prime+1
sta (Prime_ptr,x)
clc
lda Prime_ptr ; Increment the pointer in the array of primes
adc #$02
sta Prime_ptr
lda Prime_ptr+1
adc #$00
sta Prime_ptr+1
;
Next_Poss_prime
clc ; Increment Poss_Prime to look at the next
lda Poss_Prime ; number
adc #$01
sta Poss_Prime
lda Poss_Prime+1
adc #$00
sta Poss_Prime+1
bcc Next_PP ; Carry will be set when we reach $10000
;
; Ends when it has found all the primes up to 65535
;
;
page 4
lda #$00
sta $a001 ; Turns off the LED after the code finishes
;
DONE JMP DONE ; Endless loop at end to halt execution
;
;
;
; --------------------------------------------------------------------------
; Find the Modulus Remainder of Poss_Prime and number in A
; --------------------------------------------------------------------------
; Input Regs: .A Number being divided into the Possible Prime
; Poss_Prime contains the number being tested for primeness
; Output Regs: .A Modulo remainder
;
Mod ldx Poss_Prime ; Transfer Poss_Prime to Temp
stx Temp
ldx Poss_Prime+1
stx Temp+1
ldx #$00 ; Set the bit shifting counter to #$00
stx Shift
;
; Compare A to the upper byte of Temp
;
Compare sec ; Compare to see if the .A is greater than
cmp Temp+1 ; (equal to) the high byte of Temp
bcs A_Bigger
;
; If the accumulator is smaller than the upper byte of Temp then shift it
; until it is bigger or it overflows the highest bit
;
clc
rol a
bcc Not_off_end
;
; It has overflowed the highest bit, unroll it by one position
;
ror a
sta TempArg
jmp Start_Mod
;
; Not overflowed yet, go and compare it to Temp+1 again
;
Not_off_end inc Shift
jmp Compare
;
; If the accumulator is bigger and it has been shifted then unshift by one
; bit
;
A_Bigger ldx Shift
cpx #$00
sta TempArg
beq Start_Mod
clc
ror a
dec Shift
sta TempArg
;
page 5
; If the accumulator was smaller than the highest byte of Temp it now
; has been shifted to strip off the high bit at least
; If the accumulator was larger than the highest byte then proceed with the
; regular modulus shift and subtracts
;
Start_Mod lda Temp+1
sec
cmp TempArg
bcc Dont_Subt
;
; Subtract as a stage of division
;
sbc TempArg
sta Temp+1
;
Dont_Subt
;
; We would now like to shift the TempArg relative the Temp
; 1) Shift is greater than zero - accumulator was shifted - unshift it
; 2) Shift Temp - if shift reaches -8 then we are out of Temp and
; what we have left is the modulus --RTS
;
lda Shift
bmi Sh_Temp ; Case 2
beq Sh_Temp
; Case 1
clc
ror TempArg
dec Shift
jmp Start_Mod
;
Sh_Temp cmp #$f8
bne Continue
lda Temp+1 ; This is the Modulus
rts
Continue dec Shift
clc
rol Temp
rol Temp+1
jmp Start_Mod
;
.ORG $FFFC
.WORD $E000
.END
;
;
;
;==============================================================================
;******************************************************************************
;==============================================================================
;
;
page 6
;=============================================================================
; The Multitasking 6502 - See you 6502 do several things at once
;=============================================================================
; This relies on the assumption that there is a source of IRQ's out there
; that is repetitive and each task is allotted time between each IRQ.
; Process 1 is started automatically by the RESET signal.
; Any process can extend its life for a while (if it is doing something
; important) by setting the SEI and then CLI after the important section.
;
;
;
.ORG $E000
SEI ; INITIALIZING THE STACK POINTER
LDX #$FF
TXS
;
LDX #$00
LDY #$00
Delay DEX
BNE Delay
DEY
BNE Delay
;
; Each Process has a reserved space in memory starting with process 1 at
; $0200-$03FF, process 2 at $0400-$05FF. With this model, an 8K RAM can
; support 15 such processes provided none of the RAM outside zero page and
; stack is used during the execution of a particular process.
;
M_box = $F0 ; A Mailbox used to communicate between processes
Com1 = $F8 ; User Communications Channel to other processes
Com2 = $F9
Temp = $FA ; A temporary variable used during SWAPS and SPAWNS
Proc_Ptr = $FB ; Pointer to the reserved space of the current process
Proc = $FC ; Current process number
Proc_N = $FE ; Actual Number for active Processes
Proc_M = $FF ; Maximum Number of Processes that have been concurrent
;
; A Process Record Consists of:
; Offset Purpose
; ------ -------
; 00 Priority
; 01 Priority Counter
; 02 Accumulator
; 03 X Register
; 04 Y Register
; 05 Stack Pointer
;
; 10-FF Zero Page Memory from $00-$EF
; 100-1FF System Stack Space
;
lda #$01 ; Initialize the start up process as 1
sta Proc
sta Proc_N ; Set the number of processes to 1
sta $0200 ; Set the priority of process 1 to 1
lda #$00
sta $0201 ; Set the priority counter of process 1 to 0
page 7
lda #$00
sta Proc_Ptr ; Initialize the process pointer to point to
lda #$02 ; Process 1 reserved space $0200-$03FF
sta Proc_Ptr+1
JMP Start_Code
;
;===========================================================================
; IRQ Subroutine to Swap Tasks
;===========================================================================
;
IRQ_VECT sta Temp ; Store .A Temporarily
;
; If there is only one active process currently then just return
;
lda Proc_N
cmp #$01
bne Cont_Swap1
lda Temp
rti
;
; Continue there is more than one Process
;
Cont_Swap1 tya
pha
;
; Check process priority counter. If it equals the priority of the process
; then attempt to swap in another process
;
ldy #$00
lda (Proc_Ptr),y ; Load Priority Number
beq Swap_In ; If 'killed' process then just swap in another
iny
inc (Proc_Ptr),y ; Increment Priority Counter
cmp (Proc_Ptr),y
beq Cont_Swap2
;
; Not done this Process, Return
;
pla
tay
lda Temp
rti
;
; Other Processes available and this one is done: S W A P O U T
;
Cont_Swap2 pla
ldy #$04
sta (Proc_Ptr),y ; Save .Y
dey
txa
sta (Proc_Ptr),y ; Save .X
dey
lda Temp
sta (Proc_Ptr),y ; Save .A
ldy #$05
tsx
txa
sta (Proc_Ptr),y ; Save .SP
page 8
;
; Swap Zero Page ($00-$EF) to (Proc_Ptr + $10-$FF)
;
ldy #$00
lda #$10
sta Proc_Ptr
Out_Zero lda $00,y
sta (Proc_Ptr),y
iny
cpy #$f0
bne Out_Zero
;
; Swap System Stack
;
lda #$00
sta Proc_Ptr
inc Proc_Ptr+1
tsx
txa
tay
Out_Stack iny
beq Swap_In
lda $0100,y
sta (Proc_Ptr),y
jmp Out_Stack
;
;
; Look for the next process to swap in
;
Swap_In
Another lda Proc ; Looking for another process to Swap in
cmp Proc_M
bne Not_End
;
; Go back to Process #1
;
lda #$01
sta Proc
lda #$02
sta Proc_Ptr+1
jmp Check_Proc
;
; Go to the next Process
;
Not_End clc
lda Proc_Ptr+1
adc #$02
sta Proc_Ptr+1
inc Proc
;
; Check this Process if Non-Active, go try another
;
ldy #$00
lda (Proc_Ptr),y
beq Another
;
page 9
; Found an Acceptable Process: S W A P I N
;
;
; Get the Stack Pointer
;
ldy #$05
lda (Proc_Ptr),y ; Restore .SP
tax
txs
;
; Swap In Zero Page ($00-$EF) to (Proc_Ptr + $10-$FF)
;
ldy #$00
lda #$10
sta Proc_Ptr
In_Zero lda (Proc_Ptr),y
sta $00,y
iny
cpy #$f0
bne In_Zero
;
; Swap System Stack
;
lda #$00
sta Proc_Ptr
inc Proc_Ptr+1
tsx
txa
tay
In_Stack iny
beq Restore_Regs
lda (Proc_Ptr),y
sta $0100,y
jmp In_Stack
;
; Restore all of the system registers
;
Restore_Regs
lda #$00
sta Proc_Ptr
dec Proc_Ptr+1
ldy #$01 ; Set Priority Counter to 0
sta (Proc_Ptr),y
iny
lda (Proc_Ptr),y ; Temporarily store .A
sta Temp
iny
lda (Proc_Ptr),y ; Restore .X
tax
iny
lda (Proc_Ptr),y ; Restore .Y
tay
lda Temp ; Restore .A
rti
;--------------------- Done the Swap ----------------------
;
;
;
page 10
;==========================================================
; Spawn a New Process
;==========================================================
; PHA Process PCH
; PHA Process PCL
; PHA Process Priority
; JSR Spawn High
; Spawn Low
;
;
Spawn lda Proc_Ptr+1 ; Store Current Process Pointer
sta Temp
lda Proc ; Store Current Process Number
pha
lda #$01 ; Set Process Pointer and Number to 1
sta Proc
lda #$02
sta Proc_Ptr+1
;
Free_Check ; See if there is an old process number no longer
ldy #$00 ; in use
lda (Proc_Ptr),y
beq Got_Free
inc Proc
clc
lda Proc_Ptr+1
adc #$02
sta Proc_Ptr+1
lda Proc_M
sec
cmp Proc
bcs Free_Check
inc Proc_M ; Have to create an extra Process
inc Proc_N
;
; Ok we are clear, Create this Process
;
Got_Free tsx ; Get the current stack pointer
txa
clc
adc #$05
tax ; Set x to point at Priority
;
ldy #$00
lda $0100,x ; Transfer Priority to Process Space
sta (Proc_Ptr),y
;
ldy #$05 ; Set .sp = #$FC
lda #$FC
sta (Proc_Ptr),y
;
ldy #$02 ; Set the accumulator to 1 to indicate: START
lda #$01 ; to the new process
sta (Proc_Ptr),y
;
inc Proc_Ptr+1 ; To point into stack swap space for this process
;
page 11
lda #$00 ; Processor Status Register, for this process
ldy #$FD
sta (Proc_Ptr),y
;
inx
lda $0100,x ; Load PCL
iny
sta (Proc_Ptr),y ; Put into (swapped) Stack
;
inx
lda $0100,x ; Load PCH
iny
sta (Proc_Ptr),y ; Put into (swapped) Stack
;
lda Temp ; Set Pointer back to original (Spawner) process
sta Proc_Ptr+1
;
lda Proc ; Take Spawned Process number and put in Temp
sta Temp
;
pla ; Restore Spawned Process number
sta Proc
;
pla ; Pull 'Spawn' return address from stack
tax
pla
tay
;
pla ; Pull Spawn data out of the stack
pla
pla
;
tya ; Push the Return Address back to the stack
pha
txa
pha
lda Temp ; Return Spawned Process Number
rts
;-------------- Done Spawn -----------------
;
;
;
;=============================================================
; Kill a Process
;=============================================================
;
; Input Registers : NONE
; Output Registers: NEVER RETURNS IF KILL IS SUCCESSFUL
;
Kill lda Proc_N
cmp #$01 ; Can't Clear Last Process
bne Ok_More
rts
Ok_More ldy #$00 ; OK Kill the Process, put a 0 in Priority
tya
sta (Proc_Ptr)
;
page 12
dec Proc_N ; One Less Process
;
lda Proc ; If we are clearing 'Maximum' Process then
cmp Proc_M ; then reduce maximum
beq Reduce_Max
jmp Swap_In ; Otherwise Go swap another in
;
Reduce_Max
dec Proc
dec Proc_M
dec Proc_Ptr+1
dec Proc_Ptr+1
lda (Proc_ptr),y
beq Reduce_Max
jmp Swap_In
;---------------------- Done Clear a Process ---------------------------
;
;
;
;
;=======================================================================
; An Example Spawnable Process
;=======================================================================
; Input Registers: .A = #$00 Means that we just want the address of
; (JSR Child) this process so that the process swapper
; will know where to start.
;
; (RTI to CHILD1) .A = #$01 Means that the process swapper has signalled
; this process to actually start
;
Child jsr Child1
Child1 cmp #$00
bne Go_For_It
;
; Process was called to get its start up address
;
pla ; Grab Child1 start up address
clc
adc #$01 ; Remember that an RTS return address points at the
tax ; last byte of the JSR statement.
pla ; RTI return addresses point to the first byte of the
adc #$00 ; next instruction to be executed
tay
;
pla ; Save Return Address to program calling Child
sta Temp
pla
sta Proc_Ptr
;
tya ; Push Child1 RTI address
pha
txa
pha
;
lda Proc_Ptr ; This Pushes the calling program's return address
pha ; back into the stack
lda Temp
pha
;
page 13
lda #$00 ; Returns Proc_Ptr(low) to #$00 after its use as a
sta Proc_Ptr ; Temporary variable
rts
;
; Spawned Process actually starts:
; Note that PLA's are not required to get rid of the JSR Child1 start up
; address since the RTI address pushed in points to Child1 NOT Child
Go_For_It
Body of the spawned process
;
;
;=======================================================================
; An Example of a Kill of the present Process
;=======================================================================
;
{ User Code }
;
sei
jsr Kill ; This should kill the process unless it is the
; only process
cli
;
; This is the only process
;
{ More user code }
;
;
;
;=======================================================================
; Start of User Code
;=======================================================================
Start_Code
{ Your first process goes here }
;
;
; Example Spawn of Process 'Child'
;
sei ; Prevent swap attempts during process creation
lda #$00
jsr Child ; Request Address for Child1
;
lda #Priority
pha ; Push Priority into the stack
;
jsr Spawn ; Ask the Process Swapper to set 'Child1' up in
; the swap schedule
rol a
sta Ptr+1 ; Set pointer to the Child process zero page
lda #$10 ; reserved area
sta Ptr
;
page 14
; The Spawn call returns the process number. If there is some initial data
; or a pointer that this process would like to pass to 'Child1' then the
; address of its ZERO PAGE reserved data space is pointed to by '(Ptr),y'.
; Once the data has been transferred:
;
cli ; Re-enable swap attempts
;
;
;
;============================================================================
; Example of Taking full control of execution temporarily
;============================================================================
;
sei ; Disable swaps
{ User Code }
cli ; Re-enable swaps
;
;
;
;============================================================================
; Example of taking full control by Killing all other processes
;============================================================================
;
Ptr = $00
K_Proc = $02
;
sei ; Disable swaps
;
lda #$00 ; Set Pointer to $0200
sta Ptr
lda #$02
sta Ptr+1
;
lda #$01 ; Set Kill Process counter to 1
sta K_Proc
;
Top lda Proc
cmp K_Proc
beq Don_t_Kill
ldy #$00
tya
sta (Ptr),y
;
Don_t_Kill
cmp Proc_M
beq Done_Kill
inc Ptr+1
inc Ptr+1
inc K_Proc
jmp Top
;
page 15
Done_Kill
lda #$01
sta Proc_N
lda Proc
sta Proc_M
cli ; Note that this is optional, if we know that there
; are no other processes we could prevent swap decisions
; by not clearing the IRQ mask.
;
{ More code that will not be swapped out }
;
;
;
.ORG $FFFC
.WORD $E000
.WORD IRQ_VECT
.END
;
; -------------------- Done Multitasking example -------------------------