==================================================================== DR 6502 AER 201S Engineering Design 6502 Execution Simulator ==================================================================== Supplementary Notes By: M.J.Malone 6502 Assembly Code Examples =========================== The remainder of this file will be in a format acceptable to TASM for direct assembly. The following are the basic routines used in a stack based mathematics system very similar to that used in the FORTH or PostScript languages. It is a good example of addressing modes and use and reuse of the most common 6502 instructions. It introduces the user to the assembly language level maintenance of a stack, movement of data and use of the zero page. Note there may be errors in the code, it is not intended that it be cut up and included in students files. It is intended only as an example of addressing modes and instructions. ;============================================================================== ; Coding Examples for the Students of AER201S ;============================================================================== ; ; .ORG $E000 SEI ; INITIALIZING THE STACK POINTER LDX #$FF TXS ; LDX #$00 ; Initial LDY #$00 Delay DEX BNE Delay DEY BNE Delay ; ; ; ================================= ; Definitions and memory allocation ; ================================= ; Start_Page = $10 ; Stack goes from $1000-$1FFF or pages $10-$20 End_Page = $20 ; Stack_Ptr = $FE ; Address of the pointer to the next free byte in ; the stack Memory_Ptr = $FC ; Address of a pointer to a piece of data in ; memory MTemp = $FB ; Temporary Variable used in memory routines MTemp1 = $FA ; Temporary Variable used in memory routines ; Reg_Len = $08 ; Use 8 byte utility registers ; Utl_Reg0 = $00 ; Utility Register 0 Used for Math and stack Utl_Reg1 = Utl_Reg0+Reg_len ; Utility Register 1 manipulations Utl_reg2 = Utl_Reg1+Reg_Len ; Utility Register 2 ; ; ; page 2 LDA #Start_Page ; Initialize the stack pointer to point to the STA Stack_Ptr+1 ; first byte of the space allotted to be stack LDA #$00 ; space STA Stack_Ptr ; JMP Main ; Jump to the main program, what follows are ; subroutines ; ; ; ; ; ============== ; Halt execution ; ============== ; Halt JMP Halt ; In case of an error, the program jumps here ; ; ; ; ; ; ; ===================================== ; Stack Maintenance Subroutines ; ===================================== ; ; ; ; ====================================================================== ; Will a (.A) byte long number pushed into the stack overflow the stack? ; ====================================================================== ; ; Call Name: Overrun ; ; Input Variables: .A Holds the number of bytes you want to push onto the ; stack ; ; Output Variables: None. The Routine stops execution when an overflow ; occurs. Ideally it would call an error trap routine ; and give some output. ; Overrun CLC ADC Stack_Ptr ; Add the number of bytes to the current BCS Try_Page ; stack pointer low byte - if less than a page RTS ; then it could not have overrun. Try_Page LDA #$01 ; It overrun a page, check to see if the next ADC Stack_Ptr+1 ; page is part of the stack area. CMP #End_Page BEQ Halt ; If it has overrun then Halt! RTS ; otherwise RTS - everything is OK ; ; ; ; page 3 ; ======================================================================= ; Will a (.A) byte long number pulled from the stack underflow the stack? ; ======================================================================= ; ; Call Name: Underrun ; ; Input Variables: .A Holds the number of bytes you want to pull from the ; stack ; ; Output Variables: None. The Routine stops execution when an underflow ; occurs. Ideally it would call an error trap routine ; and give some output. ; Underrun STA MTemp LDA Stack_Ptr ; Subtract the number of bytes from the stack SEC ; pointer. SBC MTemp BCC Try_Page ; If it has not underrun an page, then RTS ; RTS - everything is OK Try_Page LDA Stack_Ptr+1 ; It has underrun a page - check to see if SBC #$00 ; the previous page is part of the stack. CMP #Start_Page BMI Halt ; If not then Halt - there was an underrun ; The BMI instruction assumes the stack will never be allowed 32K. It assumes ; the 'N' flag will never be set from comparing two numbers more different ; then $80 resulting from a stack of $8000 length or more - 32K RTS ; ; ; ; ; ========================================================== ; Copy .Y bytes of data from the memory pointer to the stack ; ========================================================== ; ; Call Name: Mem_to_Stk ; ; Input Variables: .Y is the number of bytes of memory to move to the stack ; Memory_Ptr points to the first address of the piece of data ; ; Output Variables: None. ; ; Mem_to_Stk TYA JSR Overrun ; Check for a Stack Overrun STY MTemp ; Store #bytes temporarily LDY #$00 F_Ag LDA (Memory_Ptr),y ; Move data from Memory to STA (Stack_Ptr),y ; stack INY CPY MTemp BNE F_Ag ; until .Y=#bytes ; LDA MTemp ; Add the number of bytes to the stack CLC ; pointer so that it points to the next ADC Stack_Ptr ; free byte of stack space page 4 STA Stack_Ptr LDA #$00 ADC Stack_Ptr+1 STA Stack_Ptr+1 RTS ; ; ; ; ; ========================================================== ; Copy .Y bytes of data from the stack to the memory pointer ; ========================================================== ; ; Call Name: Stk_To_Mem ; ; Input Variables: .Y is the number of bytes of stack data to move to memory ; Memory_Ptr points to the first address of the piece of data ; ; Output Variables: None. ; Stk_To_Mem TYA JSR Underrun ; Check for a Stack Underrun STY MTemp ; Store #bytes temporarily LDA Stack_Ptr ; Subtract to find first address of SEC ; a .Y byte length piece of data in the SBC MTemp ; stack. Nothing prevents the user from STA Stack_Ptr ; pulling a different size data piece LDA Stack_Ptr+1 ; from the stack than was pushed in. In SBC #$00 ; fact that makes the stack useful in STA Stack_Ptr+1 ; doing string manipulations. ; LDY #$00 T_Ag LDA (Stack_Ptr),y ; Move data from the stack STA (Memory_Ptr),y ; to memory INY CPY MTemp BNE T_Ag ; until .Y=#bytes ; RTS ; ; ; ; ; ================================================================ ; Copy .Y bytes of data from the memory pointer to the Utl_Reg(.X) ; ================================================================ ; ; Call Name: Mem_to_Reg ; ; Input Variables: - .Y is the number of bytes of memory to move ; to Utl_Reg(.X) from a location pointed to by ; Memory_Ptr ; - .X is the number of the Utility register ; - Memory_Ptr points to the first address of the piece of ; data ; page 5 ; Output Variables: None. ; Mem_to_Reg CPX #$03 ; .X must be less than 3 BPL JHalt CPY #Reg_Len+1 ; .Y must be less than (Reg_Len+1) BPL JHalt JMP MR_Cont JHalt JMP Halt MR_Cont TXA STY MTemp ; Store #bytes temporarily ; ; The following assumes Reg_Len=8 and must be adjusted otherwise ; CLC ASL A ; Multiply .X by 8 to get the ASL A ; first address of the Utl_Reg(.X) ASL A ; STA MTemp1 ; Store the beginning address ADC MTemp ; Add the length of Number STA MTemp ; Store the ending of move address ; LDX MTemp1 ; Load address of beginning of data TXA ; on zero page (by .X index offset) CLC ADC #Reg_Len ; Add on the length of the register to STA MTemp1 ; find the end of the register ; LDY #$00 MR_Ag LDA (Memory_Ptr),Y ; Move Data from Memory_Ptr STA Utl_Reg0,X ; to the Utl_Reg INY INX CPX MTemp ; Until .X=end of move address BNE MR_Ag ; LDA #$00 MR_Zero CPX MTemp1 ; While the entire Utl_Reg is not BEQ MR_Done ; yet full (not reached end of register), STA Utl_Reg0,X ; Put #$00's in the rest of the locations INX JMP MR_Zero ; MR_Done RTS ; ; ; ; ; ============================================================ ; Copy .Y bytes of data from Utl_Reg(.X) to the memory pointer ; ============================================================ ; ; Call Name: Reg_to_Mem ; page 6 ; Input Variables: - .Y is the number of bytes of register data to move ; to memory ; - .X is the register number ; - Memory_Ptr points to the first address of the piece of data ; ; Output Variables: None. ; Reg_to_Mem CPX #$03 ; .X must be less than 3 BPL JHalt CPY #Reg_Len+1 ; .Y must be less than Reg_Len+1 BPL JHalt JMP RM_Cont JHalt JMP Halt RM_Cont TXA STY MTemp ; Store #bytes temporarily ; ; The following assumes Reg_Len=8 and must be adjusted otherwise ; CLC ASL A ; Multiply .X by 8 to get the ASL A ; first address of the Utl_Reg(.X) ASL A ; STA MTemp1 ; Store the beginning address ADC MTemp ; Add the length of Number STA MTemp ; Store the ending address ; LDX MTemp1 ; Load address of beginning of data LDY #$00 RM_Ag LDA Utl_Reg0,X ; Move data from Utl_Reg STA (Memory_Ptr),Y ; to the Memory_Ptr INY INX CPX MTemp ; Until .X=end address BNE RM_Ag RTS ; ; ; ; ; =============================================================== ; Copy .Y bytes of data from the stack pointer to the Utl_Reg(.X) ; =============================================================== ; ; Call Name: Stk_to_Reg ; ; Input Variables: - .Y is the number of bytes of memory to move ; to Utl_Reg(.X) from a location pointed to by ; Memory_Ptr ; - .X is the number of the Utility register ; - Memory_Ptr points to the first address of the piece of ; data ; ; Output Variables: None. ; page 7 Stk_to_Reg CPX #$03 ; .X must be less than 3 BPL JHalt CPY #Reg_Len+1 ; .Y must be less than Reg_Len+1 BPL JHalt JMP SR_Cont SRHalt JMP Halt ; SR_Cont TYA JSR Underrun ; Check for a Stack Underrun STY MTemp ; Store #bytes temporarily LDA Stack_Ptr ; Subtract to find first address of SEC ; a .Y length data element in the stack SBC MTemp STA Stack_Ptr LDA Stack_Ptr+1 SBC #$00 STA Stack_Ptr+1 ; TXA ; ; The following assumes Reg_Len=8 and must be adjusted otherwise ; CLC ASL A ; Multiply .X by 8 to get the ASL A ; first address of the Utl_Reg(.X) ASL A ; STA MTemp1 ; Store the beginning address ADC MTemp ; Add the length of Number STA MTemp ; Store the ending of move address ; LDX MTemp1 ; Load address of beginning of data TXA CLC ADC #Reg_Len ; Add on the length of the register STA MTemp1 ; to find the last address and store it ; LDY #$00 SR_Ag LDA (Stack_Ptr),Y ; Move Data from Memory_Ptr STA Utl_Reg0,X ; to the Utl_Reg INY INX CPX MTemp ; until .X=end of move address BNE MR_Ag ; LDA #$00 SR_Zero CPX MTemp1 ; While the entire 8 byte Utl_Reg is not BEQ SR_Done ; yet full (not at last address), STA Utl_Reg0,X ; Put Zero's in the higher order locations INX JMP SR_Zero ; SR_Done RTS ; ; ; ; page 8 ; =========================================================== ; Copy .Y bytes of data from Utl_Reg(.X) to the stack pointer ; =========================================================== ; ; Call Name: Reg_to_Stk ; ; Input Variables: - .Y is the number of bytes of register data to move ; to memory ; - .X is the register number ; - Memory_Ptr points to the first address of the piece of data ; ; Output Variables: None. ; Reg_to_Stk CPX #$03 ; .X must be less than 3 BPL JHalt CPY #Reg_Len+1 ; .Y must be less than Reg_Len+1 BPL JHalt JMP RM_Cont JHalt JMP Halt RM_Cont TXA STY MTemp ; Store #bytes temporarily ; ; The following assumes Reg_Len=8 and must be adjusted otherwise ; CLC ASL A ; Multiply .X by 8 to get the ASL A ; first address of the Utl_Reg(.X) ASL A ; TAX ; Store the beginning address ADC MTemp ; Add the length of move STA MTemp1 ; Store the end of move address ; LDY #$00 RM_Ag LDA Utl_Reg0,X ; Move data from Utl_Reg STA (Memory_Ptr),Y ; to the Memory_Ptr INY INX CPX MTemp1 ; Until .X=end of move address BNE RM_Ag ; LDA MTemp ; Take the number of bytes moved and add CLC ; it to the old stack pointer to make it ADC Stack_Ptr ; point at the next free byte of stack STA Stack_Ptr ; space LDA #$00 ADC Stack_Ptr+1 STA Stack_Ptr+1 RTS ; ; ; ; page 9 ; =============================== ; Mathematics Subroutines ; =============================== ; ; We will assume that all one byte manipulations can be handled in the ; user software or in user subroutines and any address calculations IE ; 2 byte math can be done most quickly by specialized user routines. ; Here then are a basic set of single precision and 4 byte integer math ; subroutines. ; ; Storage format: ; ; Offset of byte: +3 +2 +1 0 ; ; Integer: SIIIIIII IIIIIIII IIIIIIII IIIIIIII S-Sign bit ; I-Integer bits ; ; Single: EEEEEEEE SMMMMMMM MMMMMMMM MMMMMMMM E-Exponent bits ; / S-Mantissa Sign ; Decimal Point M-Mantissa Bits ; ; In single precision storage, the exponent is offset by $80 and the ; mantissa is assumed to be normalize so that the mantissa begins with ; %1.MMMMMMM... . The '1' is omitted and the decimal point is assumed ; leading to one more bit of significance. IEEE single precision ; format has the sign bit precede the 8 exponent bits however this leads ; to the exponent bits not being aligned on an even byte. Since this ; would slow manipulations of the numbers on an 8 bit computer, ; requiring the exponent to be reassembled each time, it is not used ; here. ; ; Sign1 = $F9 ; Variables that keep track of the sign bits of the Sign2 = $F8 ; two arguments and the answer Ans_Sign = $F7 ; ; Exp1 = $F6 ; Variables to hold the exponent bytes of the arguments Exp2 = $F5 ; and either the common exponent (used in add and Com_Exp = $F4 ; subtract) or the answer's exponent. ; ; ; ====================================== ; Move Utl_Reg2 to Utl_Reg0 (4 byte) ; ====================================== ; ; Call name: Two_to_0 ; ; No Input or Output variables ; Two_to_0 LDA Utl_Reg2 STA Utl_Reg0 LDA Utl_Reg2+1 STA Utl_Reg0+1 LDA Utl_Reg2+2 page 10 STA Utl_Reg0+2 LDA Utl_Reg2+3 STA Utl_Reg0+3 RTS ; ; ; ; ; ====================================== ; Move Utl_Reg2 to Utl_Reg1 (4 byte) ; ====================================== ; ; Call name: Two_to_1 ; ; No Input or Output variables ; Two_to_1 LDA Utl_Reg2 STA Utl_Reg1 LDA Utl_Reg2+1 STA Utl_Reg1+1 LDA Utl_Reg2+2 STA Utl_Reg1+2 LDA Utl_Reg2+3 STA Utl_Reg1+3 RTS ; ; ; ; ; ============================= ; Internal Addition Routine ; ============================= ; ; Simple add of Utl_Reg2=Utl_Reg0 + Utl_Reg1 with no sign considerations ; In_Add CLC LDA Utl_Reg0 ADC Utl_Reg1 STA Utl_Reg2 LDA Utl_Reg0+1 ADC Utl_Reg1+1 STA Utl_Reg2+1 LDA Utl_Reg0+2 ADC Utl_Reg1+2 STA Utl_Reg2+2 LDA Utl_Reg0+3 ADC Utl_Reg1+3 STA Utl_Reg2+3 RTS ; ; ; ; page 11 ; ================================ ; Internal Subtraction Routine ; ================================ ; ; Simple subtract of Utl_Reg2=Utl_Reg0 - Utl_Reg1 with no sign considerations ; In_Subt SEC LDA Utl_Reg0 SBC Utl_Reg1 STA Utl_Reg2 LDA Utl_Reg0+1 SBC Utl_Reg1+1 STA Utl_Reg2+1 LDA Utl_Reg0+2 SBC Utl_Reg1+2 STA Utl_Reg2+2 LDA Utl_Reg0+3 SBC Utl_Reg1+3 STA Utl_Reg2+3 RTS ; ; ; ; ; ======================================== ; Internal Reverse Subtraction Routine ; ======================================== ; ; Simple subtract of Utl_Reg2=Utl_Reg1 - Utl_Reg0 with no sign considerations ; In_R_Subt SEC LDA Utl_Reg1 SBC Utl_Reg0 STA Utl_Reg2 LDA Utl_Reg1+1 SBC Utl_Reg0+1 STA Utl_Reg2+1 LDA Utl_Reg1+2 SBC Utl_Reg0+2 STA Utl_Reg2+2 LDA Utl_Reg1+3 SBC Utl_Reg0+3 STA Utl_Reg2+3 RTS ; ; ; ; ; ====================== ; Integer 4 byte Add ; ====================== ; ; Call Name: IADD4 ; page 12 ; Input Variables: The two numbers to be added are assumed to be in Utl_Reg0 ; and Utl_Reg1 ; ; Output Variables: The answer appears in Utl_Reg2. ; ; ------------------------------------------------------------------------------ . . . The remainder of the file has been omitted. . . . ------------------------------------------------------------------------------- ; ; ; .ORG $FFFC .WORD $E000 .END