==================================================================== DR 6502 AER 201S Engineering Design 6502 Execution Simulator ==================================================================== Supplementary Notes By: M.J.Malone More 6502 Assembly Code ======================= There were several instructions not introduced in the first introductory file 'START65.DOC'. The remaining 6502 instructions will be discussed here. Operator Instructions --------------------- These are instructions of only one argument and include incrementing, decrementing and bit shifts. INC arg Increases the value of 'arg' by one DEC arg Decreases the value of 'arg' by one INX Increment .X INY Increment .Y DEX Decrement .X DEY Decrement .Y ROR arg Rotates the argument and C flag bits one to the right ROL arg Rotates the argument and C flag bits one to the left LSR arg Logical Shift Right: shifts bits one to the right ASL arg Arithmetic Shift Left: shifts bits one to the left The increment and decrement instructions are very useful when dealing with pointers and vectors. Since the .X and .Y are index registers used to index within memory spaces, the INX, INY, DEX and DEY instructions are used to move on the the next or previous memory entry. The .X and .Y are also used as quick counters and here again the increment and decrement are very useful. The bit rotating and shifting instructions are used both in arithmetic operations and bit pattern manipulation for encoding and decoding. An LSR can be thought of as dividing by 2 and the ASL as multiplying by 2. Along with the ADC and SBC, these instructions are key to multiplication and division. Flag Setting and Clearing ------------------------- The programmer is able to directly influence the flags through the following statements: CLC Clears the Carry Flag SEC Sets the Carry Flag CLD Clears 'Decimal' Mode SED Sets 'Decimal' Mode CLI Clears the Interrupt Disable Flag SEI Sets Interrupt Disable Flag BRK Break Instruction CLV Clears the Overflow Flag page 2 Carry Flag The carry flag is set and cleared by arithme tic and shift statements and is influenced by comparisons. In general the flag is used as follows: In ROL, ROR, ASL and LSR statements the carry flag is used as a 9th bit in the accumulator either above the most significant bit (MSB) or below the least significant bit (LSB). In SBC and ADC statements it is used as a 9th bit above the MSB to act as a carry or a borrow. Since comparisons use a subtraction, the carry flag reflects a similar state for CMP, CPX, and CPY as for the statement SBC. The carry flag is NOT used as a borrow-from in CMP, CPX or CPY. For example using CMP to compare #$05 and #$05 results in a result of zero regardless of the state of the carry flag before the comparison. After the comparison however, if a borrow was not required to do the comparison the carry flag will be set indicating no borrow has occurred. The above comparison will result in the carry flag being set. Increment instructions DO NOT influence the carry flag, nor do AND, ORA or EOR. Negative and Zero Flags Neither the negative or zero flag can be explicitly set or cleared in a SEx or CLx instruction. All instructions that move data to a location inside the processor or compare data within in any way except pulling from the stack influence the negative and zero flags. That means all instructions except JMP, JSR, RTS, Sets, Clears, Pushes, Pulls, STA, STX, STY and Branches alter the negative and zero flags. If the data being transferred is zero or if a calculation results in the number zero, regardless of the state of the carry flag, the zero flag will be set (Z=1). The negative flag will have the same logic state as the MSB of the data moved or the result of the calculation. As a result, the negative and zero flags will never both be set at the same time because a zero has all bits including the MSB clear. The zero flag and testing for zero: BNE (Z=0?) and BEQ (Z=1?) are quite straight forward. The negative flag has some peculiar properties. First note that there is no such thing as a negative number in the 6502. If required to judge if a number is negative, the 6502 will assume all numbers between #$FF and #$80 are negative correspond to the numbers -#$01 to -#$80 respectively. The numbers between #$00 and #$7F are assumed positive corresponding to +#$00 and +#$7F respectively. The comparison and subtraction instructions often cause students confusion in relation to the negative flag. Often a following will be required: ; ; If arg1<arg2 then **** ; lda arg1 cmp arg2 bmi **** ; On the surface this seems to be ok but remember the limits on a negative number for the 6502. If arg1=#$02 and arg2=#$05 then the result is #$FD which falls into the range #$FF to #$80 and hence is considered negative and the N flag is set. Looking at this from the point of view of the 6502, we move the logic value of the MSB of page 3 #$FD to the N flag and we get N=1. 2-5<0 and N=1 so there is no problem. The problem comes when arg1=#$02 and arg2=#$85 (meant to be the positive number +#$85) since the result is #$7D. We know that #$02-#$85<0 but the MSB of #$7D is 0 so as a result N=0 and the above code example would malfunction. The important thing to remember is that the negative flag does not actually tell you if a number is negative but instead whether the highest bit is set or not. Decimal Mode Flag When set, the 6502 works in a 'decimal' mode when performing the ADC or SBC instructions. This mode uses the binary coded decimal (BCD) storage format. The BCD representation of the decimal number 25 would be #$25 or #%0010 0101. In BCD mode the 6502 will use only 100 of the possible $100 (256) states for the value in the accumulator. The 6502 automatically does all adjustments to cause results to be in BCD mode and all arguments must be valid BCD coded numbers. For example #$55+#$27 in BCD mode = #$82. There is absolutely nothing magic about decimal mode and almost as little of interest. One common bug in an IRQ interrupt program is forgetting to clear the decimal flag before doing any calculations. If the main program uses decimal mode sometimes the IRQ may occur when the decimal mode is in effect. This can cause very unusual problems if the programmer is unaware of the problem. Interrupt Masking Flag If your IRQ (interrupt request) pin is connected to a source of interrupt pulses then the processor will be interrupted and will begin executing the interrupt program. The interrupt program is a separate program that the programmer must write if interrupts are used. The IRQ is a maskable interrupt meaning that the software can instruct the processor to ignore it. The method of doing this involves setting the interrupt disable bit in the processor status register. The instruction SEI prevents interrupts from occurring and CLI allows them. The 6502 has a second interrupt request pin, the NMI which stands for non-maskable interrupt request. SEI and CLI have no effect on this interrupt: it will always be obeyed. On the 65C02 the RST (reset) pin behaves as a particular type of interrupt similar to the NMI. Break Flag There is one instruction in the 6502's set that is very confusing to many students; it is the BRK break instruction. The break does not actually stop the execution of the processor like a HALT type instruction might. The 6502 has no instruction that will HALT execution. There are many undefined operation codes on the NMOS 6502 that do unpredictable things and some that crash the processor. These 'crash codes' could be considered HALT statements since after them nothing happens. The break instruction simulates a hardware IRQ with the one difference that it sets the B=1 break flag in the processor status register. Since there are no statements that directly test the break flag this will be explained later in the stack operations section. The programmer must be aware that if they use both BRK instructions in there code and IRQ interrupts, the IRQ program should test the break flag to discover how the routine was initiated. page 4 Overflow Flag The overflow flag is used for the ADC and SBC instructions only. It represents an arithmetic overflow. Note the overflow flag can also be set by a pulse on the SO (set overflow) pin of the 6502 and if used cleverly can be a very fast input pin in a polling loop. Flag Summary It is the interrelation of instructions and the system flags that makes efficient code. In the following loop it is the interrelation between the DEx instruction and the Z zero flag that allows branching without a comparison statement, saving at least 3 clock cycles per iteration resulting in a execution time 38% faster. ; ldx #$00 ldy #$00 Delay ??????? inx bne Delay inx bne Delay ; This routine is often used for software delays where clock cycle counts are important but even more important is its use in counting loops for measurements. If the ?'s were replaced by some polling instructions testing say the overflow flag set by some external source it could be a very powerful measurement tool. The faster the counting executes, the more accurate the measurement of time periods. Mastery of the flags is an important ingredient in the efficient use of 6502 assembly code. Stack Operations ---------------- The stack is a reserved area in memory from $0100 to $01FF which is referred to as page 1 of the 6502 memory. The .SP stack pointer register is used to index within this space. The stack pointer starts at #$FF which points to $01FF. The stack pointer points to the next free stack location. The stack pointer decrements as the stack fills and increments as the stack empties. There is nothing (in hardware) to prevent the stack from being pushed below #$00 to #$FF again or being pulled from #$FF around to #$00. When a Push instruction is executed, the pushed data is put in the free stack space that the pointer is currently pointing to and the stack pointer is decremented by one. In a pull operation the stack pointer is incremented and the data is read from the location that is being pointed to. PHA Pushes the .A into the stack PLA Pulls .A out of the stack PHP Pushes the status register (.R) into the stack PLP Pulls the .R out of the stack page 5 PHX *Pushes the .X register into the stack PLX *Pulls the .X register out of the stack PHY *Pushes the .Y register into the stack PLY *Pulls the .Y register out of the stack *65C02 only For those unfamiliar with the use of stacks, it is recommended that computer theory text be consulted if a full description is desired. In practice in 6502 assembly language the stack is used by the hardware in the JSR-RTS and IRQ/NMI/BRK/RST-RTI instructions. The user has direct access to the data in the stack through the PHx and PLx instructions. The most important concept is balancing the number of pushes on any program path with the number of pulls in the path. This is required so that the stack will not continue filling endlessly, overflowing or empty past the point that useful data was first pushed in. It is also necessary to balance the number of pushes and pulls on a particular program path within all subroutines. This is required since the stack is used for the return addresses for the subroutine call. If any user data is pushed on top of the return address then it must be pulled off before the RTS statement so that the program/subroutine nesting structure, recorded in the return addresses in the stack, is preserved. IRQ/NMI and BRK routines require the stack as well, pushing in not only the return address but the processor status register (.R). Though it is not required, I cannot imagine an IRQ routine where the user does not first push the .A, .X and .Y registers into the stack as well to preserve the processor context. The reason for this is simple. The IRQ could come at any time, perhaps in the middle of a calculation where all of the register values are critical. If the IRQ must preserve the values of all registers so that upon return the main program continues as if the IRQ had not occurred. In this way it is possible to make the execution of IRQ programs transparent to the main program. The stack can also be used with the combination of PHP \ PLA to transfer data from the processor status register to the accumulator. This is very useful to test flags that cannot be directly tested in a branch instruction. The break or decimal flags are examples of such flags. Miscellaneous ------------- BRK Break Instruction BIT arg Bit Test *TRB arg Test and Reset Bits *TSB arg Test and Set Bits RTI Return from Interrupt NOP No Operation *65C02 only The BRK break instruction, as said before under the flags and stack discussion, does not actually halt the execution of the page 6 processor. The following instruction will effectively halt the execution of the processor: ; Halt JMP Halt ; This is of course nothing more than an endless loop. The break instruction actually simulates an IRQ in software with the one difference that it also sets the B break flag in the processor status register. When a BRK instruction is reached, a $00 as an operation code, the processor pushes the program counter and the processor status register onto the stack. The processor then loads the $FFFE-FFFF IRQ vector position into the program counter and begins executing instructions. The BIT bit test instruction is somewhat strange. It first fetches the argument and copies its 7th (MS) bit into the negative flag and its 6th bit into the overflow flag. It then performs a logical AND with the accumulator discarding the result but setting the zero flag accordingly. The TRB test and reset bits instruction is similar to the BIT instruction in that bits 7 and 6 of the argument are copied into the N and V flags. A logical AND with the complement of the accumulator resets the bits of the argument that are set to a one in the accumulator. The Z flag is set according to the result and the result is stored back into the memory location referred to in the argument. The TSB test and set bits instruction is similar to the TRB instruction in that bits 7 and 6 of the argument are copied into the N and V flags. A logical OR with the accumulator sets the bits of the argument that are set to a one in the accumulator. The Z flag is set according to the result and the result is stored back into the memory location referred to in the argument. The TSB and TRB instructions were added to the instruction set to allow multiple processor systems to share resources through a series of semaphores. The RTI return from interrupt instruction returns the processor from an IRQ, NMI or BRK interrupt. On the 65C02, since the RST line is treated by the processor as just another interrupt, it is possible to RTI to the program that was running previous to the signal on the reset line. RTI retrieves processor status register and the program counter from the stack and returns to executing the original program. The NOP no operation instruction is just that, no operation. If the processor encounters this instruction it does nothing for two machine cycles. The NOP instruction does not change the processor status register. page 7 65C02 Expansions ---------------- STZ arg Store #$00 to the memory location 'arg' BRA offset Always do a relative branch by 'offset' bytes PHX Push .X into the stack PHY Push .Y into the stack PLX Pull .X from the stack PLY Pull .Y from the stack TRB arg Test and Reset Bits TSB arg Test and Set Bits All of these instructions have been explained in the previous sections explaining the 6502 instruction set. Rockwell 65C02 Expansions ------------------------- BBRx arg,offset BBSx arg,offset RMBx arg SMBx arg These instructions are present only on the Rockwell version of the 65C02. Though some students have found this version of the processor, there is no guarantee that the next batch of processors ordered will be of the Rockwell variety. As a result it is not recommended that students base their software structures on the presence of these instructions since last minute problems may result in a change of processor. Rockwell added four types of instructions to their processor to make the use of bit fields easier. For each instruction, the numbers 0-7 must be substituted for the 'x' to give the actual operation codes. There are therefore eight RMBx instructions: RMB0, RMB1, RMB2, RMB3, RMB4, RMB5, RMB6, and RMB7. Note that these four instruction types are the only 6502 instructions that do not conform to the standard of 3 character mnemonic codes. The BBRx and BBSx are branch statements. These statements test a particular bit (bit 'x') of the argument which must be on the zero page and branches if it is set (BBSx) or branches if it is reset (BBRx). The branch is a relative branch by 'offset' bytes in the same way as regular branches. The RMBx reset memory bit and SMBx set memory bit instructions are used to reset or set individual memory bits anywhere on the zero page of memory. The argument is the memory location and the 'x' specifies the bit number to be manipulated.