As you can see from the preceding descriptions the instruction set of the 6502 is quite basic, having only simple 8 bit operations. Complex operations such as 16 or 32 bit arithmetic and memory transfers have to be performed by executing a sequence of simpler operations. This sections describes how to build these algorithms and is based on code taken from my macro library.
If you find any bugs in the code, have routines to donate to the library, or can suggest improvements then please mail me.
The 6502 processor expects addresses to be stored in 'little endian' order, with the least significant byte first and the most significant byte second. If the value stored was just a number (e.g. game score, etc.) then we could write code to store and manipulate it in 'big endian' order if we wished, however the algorithms presented here always use 'little endian' order so that they may be applied either to simple numeric values or addresses without modification.
The terms 'big endian' and 'little endian' come from Gulliver's Travels. The people of Lilliput and Blefuscu have been fighting a war over which end of an boiled egg one should crack to eat it. In computer terms it refers to whether the most or least significant portion of a binary number is stored in the lower memory address.
To be safe the algorithms usually start by setting processor flags and registers to safe initial values. If you need to squeeze a few extra bytes or cycles out of the routine you might be able to remove some of these initializations depending on the preceding instructions.
Probably the most fundamental memory operation is clearing an area of memory to an initial value, such as zero. As the 6502 cannot directly move values to memory clearing even a small region of memory requires the use of a register. Any of A, X or Y could be used to hold the initial value, but in practice A is normally used because it can be quickly saved and restored (with PHA and PLA) leaving X and Y free for application use.
; Clearing 16 bits of memory _CLR16 LDA #0 ;Load constant zero into A STA MEM+0 ;Then clear the least significant byte STA MEM+1 ;... followed by the most significant ; Clearing 32 bits of memory _CLR32 LDA #0 ;Load constant zero into A STA MEM+0 ;Clear from the least significant byte STA MEM+1 ;... up STA MEM+2 ;... to STA MEM+3 ;... the most significant
Moving a small quantity of data requires a register to act as a temporary container during the transfer. Again any of A, X, or Y may be used, but as before using A as the temporary register is often the most practical.
; Moving 16 bits of memory _XFR16 LDA SRC+0 ;Move the least significant byte STA DST+0 LDA SRC+1 ;Then the most significant STA DST+1 ; Moving 32 bits of memory _XFR32 LDA SRC+0 ;Move from least significant byte STA DST+0 LDA SRC+1 ;... up STA DST+1 LDA SRC+2 ;... to STA DST+2 LDA SRC+3 ;... the most significant STA DST+3
Provided the source and destination areas do not overlap then the order in which the bytes are moved is irrelevant, but it usually pays to be consistent in your approach to make mistakes easier to spot.
All of the preceding examples can be extended to apply to larger memory areas but will generate increasingly larger code as the number of bytes involved grows. Algorithms that iterate using a counter and use index addressing to access memory will result in smaller code but will be slightly slower to execute.
This trade off between speed and size is a common issue in assembly language programming and there are times when one approach is clearly better than the other (e.g. when trying to squeeze code into a fixed size ROM - SIZE, or manipulate data during a video blanking period - SPEED).
; Clear 32 bits of memory iteratively _CLR32C LDX #3 LDA #0 _LOOP STA MEM,X DEX BPL _LOOP ; Move 32 bits of memory iteratively _XFR32C LDX #3 _LOOP LDA SRC,X STA DST,X DEX BPL _LOOP
Another basic operation is setting a 16 bit word to an initial constant value. The easiest way to do this is to load the low and high portions into A one at a time and store them.
; Setting a 16 bit constant _SET16I LDA #LO NUM ;Set the least significant byte of the constant STA MEM+0 LDA #HI NUM ;... then the most significant byte STA MEM+1
The simplest forms of operation on binary values are the logical AND, logical OR and exclusive OR illustrated by the following truth tables.
Logical AND (AND) | Logical OR (ORA) | Exclusive OR (EOR) | |||||||||||||||||||||||||||||
|
|
|
These results can be summarized in English as:
The tables show result of applying these operations on two one-bit values but as the 6502 comprises of eight bit registers and memory each instruction will operate on two eight bit values simultaneously as shown below.
Logical AND (AND) | Logical OR (ORA) | Exclusive OR (EOR) | |||||||||||||||||||||||||||
Value 1 |
|
|
|
||||||||||||||||||||||||||
Value 2 |
|
|
|
||||||||||||||||||||||||||
Result |
|
|
|
It is important to understand the properties and practical applications of each of these operations as they are extensively used in other algorithms.
In the 6502 these operations are implemented by the AND, ORA and EOR instructions. One of the values to be operated on will be the current contents of the accumulator, the other is in memory either as an immediate value or at a specified location. The result of the operation is placed in the accumulator and the zero and negative flags are set accordingly.
; Example logical operations AND #$0F ;Filter out all but the least 4 bits ORA BITS,X ;Insert some bits from a table EOR (DATA),Y ;EOR against some data
A very common use of the EOR instruction is to calculate the 'complement' (or logical NOT) of a value. This involves inverting every bit in the value and is most easily calculated by exclusively ORing against an all ones value.
; Calculate the complement EOR #$FF
The macro library contains reference code for 16 and 32 bit AND, ORA, EOR and NOT operations although there is very little use for them outside of interpreters.
The shift and rotate instructions allow the bits within either the accumulator or a memory location to be moved by one place either up (left) or down (right). When the bits are moved a new value will be needed to fill the vacant position created at one end of the value, and similarly the bit displaced at the opposite end will need to be caught and stored.
Both shifts and rotates catch the displaced bit in the carry flag but they differ in how they fill the vacant position; shifts will always fill the vacant bit with a zero whilst a rotate will fill it with the value of the carry flag as it was at the start of the instruction.
For example the following diagram shows the result of applying an 'Arithmetic Shift Left' (ASL) to the value $4D to give $9A.
+---+---+---+---+---+---+---+---+ Initial: | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | +---+---+---+---+---+---+---+---+ | | | | | | | | / / / / / / / / / / / / / / / / 0 / / / / / / / / / / | | | | | | | | / v v v v v v v v +---+---+---+---+---+---+---+---+ Result: C=0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | +---+---+---+---+---+---+---+---+
Whist the following shows the result of applying a 'Rotate Left' (ROL) to the same value, but assuming that the carry contained the value one.
+---+---+---+---+---+---+---+---+ Initial: | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | C=1 +---+---+---+---+---+---+---+---+ | | | | | | | | / / / / / / / / / / / / / / / / / / / / / / / / / / / / / | | | | | | | | / v v v v v v v v +---+---+---+---+---+---+---+---+ Result: C=0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 1 | +---+---+---+---+---+---+---+---+
Shifting the bits within a value (and introducing a zero as the least significant bit) has the effect of multiplying its value by two. In order to apply this multiplication to a value larger than a single byte we use ASL to shift the first byte and then ROL all the subsequent bytes as necessary using the carry flag to temporarily hold the displaced bits as they are moved from one byte to the next.
; Shift a 16bit value by one place left (e.g. multiply by two) _ASL16 ASL MEM+0 ;Shift the LSB ROL MEM+1 ;Rotate the MSB
The behavior of the right shift as rotates follows the same pattern. For example we can apply a 'Logical Shift Right' (LSR ) to the value $4D to give $26.
+---+---+---+---+---+---+---+---+ Initial: | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | +---+---+---+---+---+---+---+---+ | | | | | | | | \ \ \ \ \ \ \ \ 0 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ | | | | | | | | \ v v v v v v v v \ +---+---+---+---+---+---+---+---+ Result: | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | C=1 +---+---+---+---+---+---+---+---+
Or a 'Rotate Right' (ROR) of the same value, but assuming that the carry contained the value one to give $A6.
+---+---+---+---+---+---+---+---+ Initial: C=1 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | +---+---+---+---+---+---+---+---+ \ | | | | | | | | \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ | | | | | | | | \ v v v v v v v v \ +---+---+---+---+---+---+---+---+ Result: | 1 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | C=1 +---+---+---+---+---+---+---+---+
Not surprisingly if left shifts multiply a value by two then right shifts do an unsigned division by two. Again if we are applying the division to a multi-byte value we will typically use LSR on the first byte (the MSB this time) and ROR on all subsequent bytes.
; Shift a 16 bit value by one place right (e.g. divide by two) _LSR16 LSR MEM+1 ;Shift the MSB ROR MEM+0 ;Rotate the LSB
There are a number of applications for shifts and rotates, not least the coding of generic multiply and divide algorithms which are discussed later.
As was pointed out earlier right shifting a value two divide it by two only works on unsigned values. This is because the LSR is will always place a zero in the most significant bit of the MSB. To make this algorithm work for all two complement coded values we need to ensure that value of this bit is copied back into itself to keep the value the same sign. We can use another shift to achieve this.
; Divide a signed 16 bit value by two _DIV2 LDA MEM+1 ;Load the MSB ASL A ;Copy the sign bit into C ROR MEM+1 ;And back into the MSB ROR MEM+0 ;Rotate the LSB as normal
The 6502 processor provides 8 bit addition and subtraction instructions and a carry/borrow flag that is used to propagate the carry bit between operations.
To implement a 16 bit addition the programmer must code two pairs of additions; one for the least significant bytes and one for the most significant bytes. The carry flag must be cleared before the first addition to ensure that an additional increment isn't performed.
; 16 bit Binary Addition CLC ;Ensure carry is clear LDA VLA+0 ;Add the two least significant bytes ADC VLB+0 STA RES+0 ;... and store the result LDA VLA+1 ;Add the two most significant bytes ADC VLB+1 ;... and any propagated carry bit STA RES+1 ;... and store the result
Subtraction follows the same pattern but the carry must be set before the first pair of bytes are subtracted to get the correct result.
; 16 bit Binary Subtraction SEC ;Ensure carry is set LDA VLA+0 ;Subtract the two least significant bytes SBC VLB+0 STA RES+0 ;... and store the result LDA VLA+1 ;Subtract the two most significant bytes SBC VLB+1 ;... and any propagated borrow bit STA RES+1 ;... and store the result
Both the addition and subtraction algorithm can be extended to 32 bits by repeating the LDA/ADC/STA or LDA/SBC/STA pattern for two further bytes worth of data.
The traditional approach to negating a twos complement number is to reverse all the bits (by EORing with $FF) and add one as shown below.
; 8 bit Binary Negation CLC ;Ensure carry is clear EOR #$FF ;Invert all the bits ADC #1 ;... and add one
This technique works well with a single byte already held in the accumulator but not with bigger numbers. With these it is easier just to subtract them from zero.
; 16 bit Binary Negation SEC ;Ensure carry is set LDA #0 ;Load constant zero SBC SRC+0 ;... subtract the least significant byte STA DST+0 ;... and store the result LDA #0 ;Load constant zero again SBC SRC+1 ;... subtract the most significant byte STA DST+1 ;... and store the result
The behavior of the ADC and SBC instructions can be modified by setting or clearing the decimal mode flag in the processor status register. Normally decimal mode is disabled and ADC/SBC perform simple binary arithmetic (e.g. $99 + $01 => $9A Carry = 0), but if the flag is set with a SED instruction the processor will perform binary coded decimal arithmetic instead (e.g. $99 + $01 => $00 Carry = 1).
To make the 16 bit addition/subtraction code work in decimal mode simply include an SED at the start and a CLD at the end (to restore the processor to normal).
; 16 bit Binary Code Decimal Addition SED ;Set decimal mode flag CLC ;Ensure carry is clear LDA VLA+0 ;Add the two least significant bytes ADC VLB+0 STA RES+0 ;... and store the result LDA VLA+1 ;Add the two most significant bytes ADC VLB+1 ;... and any propagated carry bit STA RES+1 ;... and store the result CLD ;Clear decimal mode
Binary coded values are more easily converted to displayable digits and are useful for holding numbers such as high scores.
; Print the BCD value in A as two ASCII digits PHA ;Save the BCD value LSR A ;Shift the four most significant bits LSR A ;... into the four least significant LSR A LSR A ORA #'0' ;Make an ASCII digit JSR PRINT ;... and print it PLA ;Recover the BCD value AND #$0F ;Mask out all but the bottom 4 bits ORA #'0' ;Make an ASCII digit JSR PRINT ;... and print it
Another use for BCD is in the conversion of binary values to decimal ones. Some algorithms perform this conversion by counting the number of times that 10000's, 1000's, 100's, 10's and 1's can be subtracted from the binary value before it underflows, but I normally use a simple fixed loop that shifts the bits out of the binary value one at a time and adds it to an intermediate result that is being doubled (in BCD) on each iteration.
; Convert an 16 bit binary value into a 24bit BCD value BIN2BCD LDA #0 ;Clear the result area STA RES+0 STA RES+1 STA RES+2 LDX #16 ;Setup the bit counter SED ;Enter decimal mode _LOOP ASL VAL+0 ;Shift a bit out of the binary ROL VAL+1 ;... value LDA RES+0 ;And add it into the result, doubling ADC RES+0 ;... it at the same time STA RES+0 LDA RES+1 ADC RES+1 STA RES+1 LDA RES+2 ADC RES+2 STA RES+2 DEX ;More bits to process? BNE _LOOP CLD ;Leave decimal mode
One final odd use of decimal arithmetic is the conversion of hexadecimal digits to printable ASCII characters. The usual way to perform this conversion is to add $30 to the digit ($00 - $0F) to make an intermediate result which is then examined to see if it is greater than or equal to $3A. If it is then an additional $06 is added to make the result fall in the range $41 - $46 (e.g. 'A' - 'F').
; Convert a hex digit ($00-$0F) to ASCII ('0'-'9' or 'A'-'F') HEX2ASC ORA #$30 ;Form the basic character code CMP #$3A ;Does the result need adjustment? BCC .+4 ADC #$05 ;Add 6 (5 and the carry) if needed
It turns out that in decimal mode the processor does basically the same correction after an addition and with the right arguments we can convert the digit to its ASCII character without performing any comparisons as shown in the following code.
; Convert a hex digit ($00-$0F) to ASCII ('0'-'9' or 'A'-'F') HEX2ASC SED ;Enter BCD mode CLC ;Ensure the carry is clear ADC #$90 ;Produce $90-$99 (C=0) or $00-$05 (C=1) ADC #$40 ;Produce $30-$39 or $41-$46 CLD ;Leave BCD mode
Assembly programs frequently use memory based counters that occasionally need incrementing or decrementing by one. One way to achieve this would be to load the LSB and MSB in turn and add or subtract one with the ADC/SBC instructions, but the 6502 has a more efficient way to do this using INC and DEC.
Incrementing is straight forward, we just increment the least significant byte until the result becomes zero. This indicates that the calculation has wrapped round (e.g. $FF + $01 => $00) and an increment to the most significant byte is needed.
; Increment a 16 bit value by one _INC16 INC MEM+0 ;Increment the LSB BNE _DONE ;If the result was not zero we're done INC MEM+1 ;Increment the MSB if LSB wrapped round _DONE EQU *
Decrementing is a little trickier because we need to know when the least significant byte is about to underflow from $00 to $FF. The answer is to test it first by loading it into the accumulator to set the processor flags.
; Decrement a 16 bit value by one _DEC16 LDA MEM+0 ;Test if the LSB is zero BNE _SKIP ;If it isn't we can skip the next instruction DEC MEM+1 ;Decrement the MSB when the LSB will underflow _SKIP DEC MEM+0 ;Decrement the LSB
Moving data from one place to another is a common operation. If the amount of data to moved is 256 bytes or less and the source and target locations of the data are fixed then a simple loop around an indexed LDA followed by an indexed STA is the most efficient. Note that whilst both the X and Y registers can be used in indexed addressing modes an asymmetry in the 6502's instruction means that X is the better register to use if one or both of the memory areas resides on zero page.
; Move 256 bytes or less in a forward direction LDX #0 ;Start with the first byte _LOOP LDA SRC,X ;Move it STA DST,X INX ;Then bump the index ... CPX #LEN ;... until we reach the limit BNE _LOOP
The corresponding code moving the last byte first is as follows:
; Move 256 bytes or less in a reverse direction LDX #LEN ;Start with the last byte _LOOP DEX ;Bump the index LDA SRC,X ;Move a byte STA DST,X CPX #0 ;... until all bytes have moved BNE _LOOP
If the amount is even smaller (128 bytes or less) then we can eliminate the comparison against the limit and use the settings of the flags after a DEX to determine if the loop has finished.
; Move 128 bytes or less in a reverse direction LDX #LEN-1 ;Start with the last byte _LOOP LDA SRC,X ;Move it STA DST,X DEX ;Then bump the index ... BPL _LOOP ;... until all bytes have moved
To create a completely generic memory transfer we must change to using indirect indexed addressing to access memory and use all the registers. The following code shows a forward transferring algorithm which first moves complete pages of 256 bytes followed by any remaining fragments of smaller size.
_MOVFWD LDY #0 ;Initialise the index LDX LEN+1 ;Load the page count BEQ _FRAG ;... Do we only have a fragment? _PAGE LDA (SRC),Y ;Move a byte in a page transfer STA (DST),Y INY ;And repeat for the rest of the BNE _PAGE ;... page INC SRC+1 ;Then bump the src and dst addresses INC DST+1 ;... by a page DEX ;And repeat while there are more BNE _PAGE ;... pages to move _FRAG CPY LEN+0 ;Then while the index has not reached BEQ _DONE ;... the limit LDA (SRC),Y ;Move a fragment byte STA (DST),Y INY ;Bump the index and repeat BNE _FRAG\? _DONE EQU * ;All done
The standard C library provides a set of functions for classifying (e.g. is letter, is digit, is ASCII, is upper case, etc.) and modifying (e.g. to upper case and to lower case) characters defined in a header called <ctype.h>. This section describes how a similar set of functions can be coded in 6502 assembler. There are two techniques that can be applied to solve this problem, namely, comparisons or look up tables.
The look up table required to implement character classification needs a byte per character. Bits within the look up table indicate how the character is to be classified (e.g. control character, printable character, white space, decimal digit, hexadecimal digit, punctuation, upper case latter or lower case letter). To test a character for a specific classification you load its description byte from the table and test for the presence of certain bits (e.g. with AND).
; Constants describing the role of each classification bit _CTL EQU $80 _PRN EQU $40 _WSP EQU $20 _PCT EQU $10 _UPR EQU $08 _LWR EQU $04 _DGT EQU $02 _HEX EQU $01 ; Test if the character in A is a control character ISCNTRL TAX LDA #_CTL BNE TEST ; Test if the character in A is printable ISPRINT TAX LDA #_PRN BNE TEST ; Test if the character in A is punctation ISPUNCT TAX LDA #_PCT BNE TEST ; Test if the character in A is upper case ISUPPER TAX LDA #_UPR BNE TEST ; Test if the character in A is lower case ISLOWER TAX LDA #_LWR BNE TEST ; Test if the character in A is a letter ISALPHA TAX LDA #_UPR|_LWR BNE TEST ; Test if the character in A is a decimal digit ISDIGIT TAX LDA #_DGT BNE TEST ; Test if the character in A is a hexadecimal digit ISXDIGIT TAX LDA #_HEX BNE TEST ; Test if the character in A is letter or a digit ISALNUM TAX LDA #_DGT|_UPR|_LWR ; Tests for the required bits in the look up table value TEST AND CTYPE,X BEQ FAIL ; Set the carry flag if any target bits were found PASS TXA SEC RTS ; Test if the character in A is in the ASCII range $00-$7F ISASCII TAX BPL PASS ; Clear the carry flag if no target bits were found FAIL TXA CLC RTS ; If A contains a lower case letter convert it to upper case TOUPPER JSR ISLOWER BCC *+4 AND #$DF RTS ; If A contains an upper case letter convert it to lower case TOLOWER JSR ISUPPER BCC *+4 ORA #$20 RTS ; The lookup table of character descriptions CTYPE DB _CTL ; NUL DB _CTL ; SOH DB _CTL ; STX DB _CTL ; ETX DB _CTL ; EOT DB _CTL ; ENQ DB _CTL ; ACK DB _CTL ; BEL DB _CTL ; BS DB _CTL|_WSP ; TAB DB _CTL|_WSP ; LF DB _CTL|_WSP ; VT DB _CTL|_WSP ; FF DB _CTL|_WSP ; CR DB _CTL ; SO DB _CTL ; SI DB _CTL ; DLE DB _CTL ; DC1 DB _CTL ; DC2 DB _CTL ; DC3 DB _CTL ; DC4 DB _CTL ; NAK DB _CTL ; SYN DB _CTL ; ETB DB _CTL ; CAN DB _CTL ; EM DB _CTL ; SUB DB _CTL ; ESC DB _CTL ; FS DB _CTL ; GS DB _CTL ; RS DB _CTL ; US DB _PRN|_WSP ; SPACE DB _PRN|_PCT ; ! DB _PRN|_PCT ; " DB _PRN|_PCT ; # DB _PRN|_PCT ; $ DB _PRN|_PCT ; % DB _PRN|_PCT ; & DB _PRN|_PCT ; ' DB _PRN|_PCT ; ( DB _PRN|_PCT ; ) DB _PRN|_PCT ; * DB _PRN|_PCT ; + DB _PRN|_PCT ; , DB _PRN|_PCT ; - DB _PRN|_PCT ; . DB _PRN|_PCT ; / DB _PRN|_DGT|_HEX ; 0 DB _PRN|_DGT|_HEX ; 1 DB _PRN|_DGT|_HEX ; 2 DB _PRN|_DGT|_HEX ; 3 DB _PRN|_DGT|_HEX ; 4 DB _PRN|_DGT|_HEX ; 5 DB _PRN|_DGT|_HEX ; 6 DB _PRN|_DGT|_HEX ; 7 DB _PRN|_DGT|_HEX ; 8 DB _PRN|_DGT|_HEX ; 9 DB _PRN|_PCT ; : DB _PRN|_PCT ; ; DB _PRN|_PCT ; < DB _PRN|_PCT ; = DB _PRN|_PCT ; > DB _PRN|_PCT ; ? DB _PRN|_PCT ; @ DB _PRN|_UPR|_HEX ; A DB _PRN|_UPR|_HEX ; B DB _PRN|_UPR|_HEX ; C DB _PRN|_UPR|_HEX ; D DB _PRN|_UPR|_HEX ; E DB _PRN|_UPR|_HEX ; F DB _PRN|_UPR ; G DB _PRN|_UPR ; H DB _PRN|_UPR ; I DB _PRN|_UPR ; J DB _PRN|_UPR ; K DB _PRN|_UPR ; L DB _PRN|_UPR ; M DB _PRN|_UPR ; N DB _PRN|_UPR ; O DB _PRN|_UPR ; P DB _PRN|_UPR ; Q DB _PRN|_UPR ; R DB _PRN|_UPR ; S DB _PRN|_UPR ; T DB _PRN|_UPR ; U DB _PRN|_UPR ; V DB _PRN|_UPR ; W DB _PRN|_UPR ; X DB _PRN|_UPR ; Y DB _PRN|_UPR ; Z DB _PRN|_PCT ; [ DB _PRN|_PCT ; \ DB _PRN|_PCT ; ] DB _PRN|_PCT ; ^ DB _PRN|_PCT ; _ DB _PRN|_PCT ; ` DB _PRN|_LWR|_HEX ; a DB _PRN|_LWR|_HEX ; b DB _PRN|_LWR|_HEX ; c DB _PRN|_LWR|_HEX ; d DB _PRN|_LWR|_HEX ; e DB _PRN|_LWR|_HEX ; f DB _PRN|_LWR ; g DB _PRN|_LWR ; h DB _PRN|_LWR ; i DB _PRN|_LWR ; j DB _PRN|_LWR ; k DB _PRN|_LWR ; l DB _PRN|_LWR ; m DB _PRN|_LWR ; n DB _PRN|_LWR ; o DB _PRN|_LWR ; p DB _PRN|_LWR ; q DB _PRN|_LWR ; r DB _PRN|_LWR ; s DB _PRN|_LWR ; t DB _PRN|_LWR ; u DB _PRN|_LWR ; v DB _PRN|_LWR ; w DB _PRN|_LWR ; x DB _PRN|_LWR ; y DB _PRN|_LWR ; z DB _PRN|_PCT ; { DB _PRN|_PCT ; | DB _PRN|_PCT ; } DB _PRN|_PCT ; ~ DB _CTL ; DEL
If we use comparisons then each function will consist of a number of comparison stages to determine if a provided character has an appropriate value. In most cases these functions are quite small but one or two of them may involve many stages (e.g. is punctuation). The execution time will vary according to the number of the tests a character is subjected to.
ISUPPER CMP #'A' BCC FAIL CMP #'Z'+1 BCS FAIL ; Drop thru here on success ISLOWER CMP #'a' BCC FAIL CMP #'z'+1 BCS FAIL ; Drop thru here on success ISALPHA CMP #'A' BCC FAIL CMP #'Z'+1 BCC PASS CMP #'a' BCC FAIL CMP #'z'+1 BCS FAIL PASS EQU * ; Drop thru here on success
Which solution is best? As in so many cases it depends on your program. If you only need one or two tests and memory size is an issue then the comparison approach will generate less code but may be slightly slower (for the complex tests), otherwise the look up table is simple and fast.
As I said in the introduction to this section all of the algorithms presented here are taken from my macro library. Coding simple algorithms like these as macros has several advantages over subroutine libraries on the 6502 processor, namely:
Another feature of the macros is that they will generate code for the 65SC02 processor using the additional instructions on that processor if the assembler defines the correct symbol. (This processor was used in the BBC Microcomputers 6502 second processor that's why I decided to support it).
The routines in the currently library are:
Macro Name | Description |
_CLR16 | Clears 16 bits of memory to zero |
_CLR32 | Clears 32 bits of memory to zero |
_CLR32C | Clears 32 bits of memory to zero iteratively |
_XFR16 | Moves 16 bits of memory |
_XFR32 | Moves 32 bits of memory |
_XFR32C | Moves 32 bits of memory iteratively |
_SET16I | Load a 16 bit constant into memory |
_NOT16 | Compute the NOT of a 16 bit value |
_NOT32 | Compute the NOT of a 32 bit value |
_NOT32C | Compute the NOT of a 32 bit value iteratively |
_ORA16 | Compute the OR of two 16 bit values |
_ORA32 | Compute the OR of two 32 bit values |
_ORA32C | Compute the OR of two 32 bit values iteratively |
_AND16 | Compute the AND of two 16 bit values |
_AND32 | Compute the AND of two 32 bit values |
_AND32C | Compute the AND of two 32 bit values iteratively |
_EOR16 | Compute the EOR of two 16 bit values |
_EOR32 | Compute the EOR of two 32 bit values |
_EOR32C | Compute the EOR of two 32 bit values iteratively |
_ASL16 | Compute the arithmetic left shift of a 16 bit value |
_ASL32 | Compute the arithmetic left shift of a 32 bit value |
_ROL16 | Compute the left rotation of a 16 bit value |
_ROL32 | Compute the left rotation of a 32 bit value |
_LSR16 | Compute the logical right shift of a 16 bit value |
_LSR32 | Compute the logical right shift of a 32 bit value |
_ROR16 | Compute the right rotation of a 16 bit value |
_ROR32 | Compute the right rotation of a 32 bit value |
_INC16 | Increment a 16 bit value |
_INC32 | Increment a 32 bit value |
_DEC16 | Decrement a 16 bit value |
_DEC32 | Decrement a 32 bit value |
_ADD16 | Add two 16 bit values |
_ADD32 | Add two 32 bit values |
_SUB16 | Subtract two 16 bit values |
_SUB32 | Subtract two 32 bit values |
_NEG16 | Negate a 16 bit value |
_NEG32 | Negate a 32 bit value |
_ABS16 | Compute the absolute value of a 16 bit value |
_ABS32 | Compute the absolute value of a 32 bit value |
_MUL16 | Calculate the 16 bit product of two 16 bit values |
_MUL16X | Calculate the 32 bit product of two 16 bit values |
_MUL32 | Calculate the 32 bit product of two 32 bit values |
_MUL16I | Generate the code for a 16 bit constant multiply |
_DIV16 | Calculate the 16 bit quotient & remainder of a 16 bit value and 16 bit dividend |
_DIV16X | Calculate the 16 bit quotient & remainder of a 32 bit value and 16 bit dividend |
_DIV32 | Calculate the 32 bit quotient & remainder of a 32 bit value and 32 bit dividend |
_CMP16 | Compare two 16 bit values |
_CMP32 | Compare two 32 bit values |
_MEMFWD | Move a block for memory a forward direction |
_MEMREV | Not Implemented |
_MEMCPY | Not Implemented |
_STRLEN | Compute the length of a 'C' style string |
_STRCPY | Copy a 'C' style string |
_STRCMP | Compare two 'C' style strings |
_STRNCMP | Not implemented |
Examine the code for more details on the macro parameters and usage.
<< Back |
|
Next >> |