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