It’s been a while since my last post. Over the last few months I was working on a converting a string to an integer and an integer to a string. This was beyond my meager 6502 abilities. I finally got it working, but had to enlist the help of Karl Wiegers. I used routines published in the July 1988 issue of Analog magazine authored by him for the Boot Camp column. I did not want to publish this post until I completely understood what his code did. I made some slight modifications to his original published code. My changes were mainly to make it easier for me to follow; allow them to be included in my libraries; and removing some pieces that I am not interested in. The source is in two pieces; Macros, and Functions. But first, the matter of the new assembly instructions that are used.
Instructions
In this post the following opcodes are introduced:
- CLC: CLear Carry flag.
- SEC: SEt Carry flag.
- SBC: SuBtract with Carry. Subtracts value from value in the accumulator.
- ADC: ADd with Carry. Adds value to the value in the accumulator.
- ORA: OR the Accumulator with value. Does an inclusive OR with the value of the accumulator and the specified value.
- ROL: ROtate Left. Moves each bit of the value in the accumulator one position to the left.
- ASL: Arithmetic Shift Left. Moves each bit in specified location (or accumulator) one position to the left.
- PHA: PusH the value in the accumulator onto the stack.
- PLA: PuLl one byte from stack and put it in the accumulator.
Macros
There are two macros; one for converting string to integer, and one for converting integer to string. As originally published this used 3 variables at fixed locations in memory. I’ve modified them to use assemble time addresses, removed an unwanted error message, and renamed variables to they make more sense to me. I put these in their own file in my library, named “A8STRMAC.LIB”. The file is SAVEd not LISTed so it can be included in other source.
Source
1000 ; Storage & Pointers 1005 MSISTR *= *+6 1010 MSIINT *= *+2 1020 MSICTR *= *+1 5000 ; ------------------------------ 5001 ; Mac.: STR2INT 5002 ; Desc: String to Integer 5003 ; Parm: 1=address of string to convert 5004 ; 2=storage address for integer 5005 ; ------------------------------ 5010 .MACRO STR2INT 5020 .IF %0<>2 5030 .ERROR "STR2INT missing parms" 5040 .ELSE 5050 LDX #$FF 5060 @STILP 5070 INX 5080 LDA %1,X 5090 STA MSISTR,X 5100 CMP #EOL 5110 BNE @STILP 5120 JSR STIVALID 5130 BCS @STIDONE 5140 JSR STR2INT 5150 BCS @STIERROR 5160 LDA MSIINT 5170 STA %2 5180 LDA MSIINT+1 5190 STA %2+1 5200 CLC 5210 BCC @STIDONE 5220 @STIERROR 5230 SEC 5240 @STIDONE 5250 .ENDIF 5260 .ENDM 5500 ; ------------------------------ 5501 ; Mac.: INT2STR 5502 ; Desc: Integer to String 5003 ; Parm: 1=address of integer to convert 5004 ; 2=storage address for string 5505 ; ------------------------------ 5510 .MACRO INT2STR 5520 .IF %0<>2 5530 .ERROR "INT2STR missing parms" 5540 .ELSE 5550 LDA %1 5560 STA MSIINT 5570 LDA %1+1 5580 STA MSIINT+1 5590 JSR INT2STR 5600 LDX #$FF 5610 @ITSLP 5620 INX 5630 LDA MSISTR,X 5640 STA %2,X 5650 CMP #EOL 5660 BNE @ITSLP 5670 .ENDIF 5680 .ENDM
Breakdown
STR2INT
- 1005-1020: Set memory space aside for the string (MSISTR), the integer (MSIINT), and a counter (MSICTR)
- 5010: Start of the STR2INT macro
- 5020: IF 2 parameters were not passed (check)
- 5030: Define the error string (though not using it here)
- 5040: Parameter check passes, continue
- 5050: Load the X register with $FF (255).
- 5060-5230: Label-Start of STILP loop (copies the string from the users string address to the functions working storage, converts its, and copies the functions integer working storage to the users integer address). It’s pretty easy to see what this is doing. It does call STIVALID to ensure the characters are valid numeric digits, and then calls the STR2INT function to do the conversion.
- 5240: Label-Start of STIERROR
- 5250: End of IF
- 5260: End of the STR2INT macro
INT2STR
- 5510: Start of the INT2STR macro
- 5520: IF 2 parameters were not passed (check)
- 5530: Define the error string (though not using it here)
- 5540: Parameter check passes, continue
- 5550-5660: Copies the integer from the users integer address to the functions working storage, calls INT2STR to do the conversion, then copies the string from the functions string working storage to the users string address.
- 5680: End of the INT2STR macro
Functions
There are four functions. They are:
- STIVALID: Looks at each character of string and checks if the character is a valid numeric digit. If it is not, the carry flag is set.
- MULT10: Multiplies integer at address (16 bit) by 10 and adds next digit in.
- STR2INT: Converts the string to a 2 byte integer. In case of error, the carry flag is set.
- INT2STR: Converts the 2 byte integer to a string.
Source
5000 ; ------------------------------ 5001 ; Func: STIVALID 5002 ; Desc: String to Integer string 5003 ; validation 5006 ; ------------------------------ 5010 STIVALID 5020 LDX #0 5030 STIVLP 5040 LDA MSISTR,X 5050 CMP #EOL 5060 BNE STIVCHK 5070 CPX #0 5080 BEQ STIVINV 5090 CLC 5100 RTS 5110 STIVCHK 5120 CMP #$30 5130 BCC STIVINV 5140 CMP #$3A 5150 BCS STIVINV 5160 AND #$0F 5170 STA MSISTR,X 5180 INX 5190 BCC STIVLP 5200 STIVINV 5210 SEC 5220 RTS 5300 ; ------------------------------ 5301 ; Func: MULT10 5302 ; Desc: Multiply Integer by 10 5305 ; ------------------------------ 5310 MULT10 5315 LDA MSIINT+1 5320 PHA 5325 LDA MSIINT 5330 ASL MSIINT 5335 ROL MSIINT+1 5340 ASL MSIINT 5345 ROL MSIINT+1 5350 ADC MSIINT 5355 STA MSIINT 5360 PLA 5370 ADC MSIINT+1 5375 STA MSIINT+1 5380 ASL MSIINT 5385 ROL MSIINT+1 5390 LDA MSISTR,X 5400 ADC MSIINT 5410 STA MSIINT 5420 LDA #0 5430 ADC MSIINT+1 5440 STA MSIINT+1 5450 RTS 5500 ; ------------------------------ 5501 ; Func: STR2INT 5502 ; Desc: String to Integer 5505 ; ------------------------------ 5510 STR2INT 5520 LDA #0 5525 STA MSIINT+1 5530 LDA MSISTR 5535 STA MSIINT 5540 LDX #1 5545 LDA MSISTR,X 5550 CMP #EOL 5560 BNE STINEXT 5570 CLC 5575 RTS 5580 STINEXT 5590 JSR MULT10 5600 BCS STICAN 5610 INX 5620 LDA MSISTR,X 5630 CMP #EOL 5640 BNE STINEXT 5650 CLC 5660 STICAN 5670 RTS 5700 ; ------------------------------ 5701 ; Func: INT2STR 5702 ; Desc: Integer to String 5705 ; ------------------------------ 5710 INT2STR 5720 LDY #0 5725 STY MSICTR 5730 ITSNEXT 5735 LDX #0 5740 ITSSLP 5750 LDA MSIINT 5755 SEC 5760 SBC ITSTABLE,Y 5770 STA MSIINT 5775 LDA MSIINT+1 5780 INY 5785 SBC ITSTABLE,Y 5790 BCC ITSADD 5795 STA MSIINT+1 5800 INX 5805 DEY 5810 CLC 5815 BCC ITSSLP 5820 ITSADD 5825 DEY 5830 LDA MSIINT 5840 ADC ITSTABLE,Y 5845 STA MSIINT 5850 TXA 5855 ORA #$30 5860 LDX MSICTR 5865 STA MSISTR,X 5870 INC MSICTR 5875 INY 5880 INY 5885 CPY #8 5890 BCC ITSNEXT 5895 LDA MSIINT 5900 ORA #$30 5910 LDX MSICTR 5915 STA MSISTR,X 5920 INX 5930 LDA #EOL 5940 STA MSISTR,X 5950 RTS 5960 ITSTABLE 5965 .WORD 10000 5970 .WORD 1000 5975 .WORD 100 5980 .WORD 10
Breakdown
STIVALID
- 5010: Label-Start of STIVALID function
- 5020: Load the X register with 0
- 5030: Label-Start of string check loop
- 5040: Load the accumulator with the character at the X offset of the string address
- 5050: Compare the accumulator with EOL
- 5060: If it is not EOL, branch to STIVCHK (check if ascii)
- 5070: Compare X register with 0 (It is EOL, is this fist character?)
- 5080: If X is 0, branch to STIVINV (It is the first character and EOL, it is invalid)
- 5090: If X is not 0, clear carry
- 5100: Return from STIVALID (exit)
- 5110: Label-Start of valid ASCII numeric digit check
- 5120: Compare accumulator with $30 (is it less than ‘0’, ascii 48)
- 5130: If it’s less than 0 branch to STIVINV (it is an invalid character)
- 5140: Compare accumulator with $3A (it it greater than ‘9’, ascii 58)
- 5150: If it’s greater than 9 branch to STIVINV (it is an invalid character)
- 5160: Clear the 4 high bits
- 5170: Save the low 4 bits to the X offset of the string address
- 5180: Increment the X register by 1
- 5190: Branch to the start of the string check loop (STIVLP)
- 5200: Label-Start of invalid character encountered
- 5210: Set carry flag to indicate error
- 5220: Return from STIVALID (exit)
MULT10
- 5315: Load the accumulator with the high byte of the integer address
- 5320: Push the accumulator onto the stack (saving the byte)
- 5325: Load the accumulator with the low byte of the integer address
- 5330-5335: Multiply the accumulator by 2
- 5340-5345: Multiply the accumulator by 2
- 5350-5375: Add to self (effectively multiplying by 5 now)
- 5380-5385: Multiply the accumulator by 2 (effectively multiplied by 10 now)
- 5390: Load accumulator with next character from string
- 5400: Add the accumulator to the integer low byte address value
- 5410: Store the accumulator at the integer low byte address
- 5420: Load the accumulator with 0
- 5430: Add accumulator to high byte address (pulls in the carry value) value
- 5440: Store the accumulator at the integer high byte address
- 5450: Return from MULT10 (exit)
STR2INT
- 5510: Label-Start of STR2INT function
- 5520: Load the accumulator with 0
- 5525: Store the accumulator at the integer high byte address (zero out high byte)
- 5530: Load the accumulator with the first character of the string
- 5535: Store the accumulator at the integer low byte address
- 5540: Load the X register with 1
- 5545: Load the accumulator with the X offset of the string address
- 5550: Compare accumulator with EOL
- 5560: Branch if Not equal to EOL to STINEXT
- 5570: If EOL, clear carry flag
- 5575: Return from STR2INT (exit)
- 5580: Label-Start of STINEXT
- 5590: Jump to SubRoutine MULT10 (to multiply by 10)
- 5600: Branch if carry set to STICAN (error, so cancel)
- 5610: Increment the X register by 1
- 5620: Load the accumulator with the X offset of the string address (next character)
- 5630: Compare the accumulator with EOL
- 5640: Branch if Not Equal to EOL to STINEXT
- 5650: Clear the carry flag
- 5660: Label-Start of STICAN
- 5670: Return from STR2INT (exit)
INT2STR
- 5710: Label-Start of INT2STR function
- 5720: Load the Y register with 0
- 5725: Store the Y register in a counter location
- 5730: Label-Start of ITSNEXT
- 5735: Load the X register with 0
- 5740: Label-Start of ITSSLP
- 5750: Load the accumulator with the value in the integer low byte
- 5755-5760: Subtract low byte of current power of 10
- 5770: Store the accumulator in the integer low byte address
- 5775: Load the accumulator with the value in the integer high byte
- 5780-Increment the Y register by 1
- 5785: Subtract the high byte of the current power of 10
- 5790: Branch on carry clear (negative) to ITSADD (restore value)
- 5795: Store the accumulator at the integer high byte address
- 5800: Increment the X register by 1 (digit counter)
- 5805: Decrement the Y register by 1
- 5810: Clear carry flag
- 5815: Branch on carry clear to ITSSLP (process next power of 10)
- 5820: Label-Start of ITSADD
- 5825: Decrement Y register by 1
- 5830: Load the accumulator with the value at the integer low byte address
- 5840: Add value of low byte current power of 10 back in
- 5845: Store the accumulator at the integer low byte address
- 5850: Transfer the X register to the accumulator
- 5855: OR the accumulator with $30 (to make it ascii)
- 5860: Load the X register with value in the counter address
- 5865: Store the accumulator at the X offset of the string address
- 5870: Increment the value in the counter address
- 5875-5780: Increment the Y register twice (point to next power of 10)
- 5885: Compare Y register with 8 (end of table?)
- 5890: Branch on carry clear to ITSNEXT (not end of table, continue)
- 5895: Load the accumulator with the value in the integer low byte address
- 5900: OR the accumulator with $30 (to make it ascii)
- 5910: Load the X register with the value in the counter address
- 5915: Store the accumulator at the X offset of the string address
- 5920: Increment the X register by 1
- 5930: Load the accumulator with EOL
- 5940: Store the accumulator at the X offset of the string address (adding the EOL to end of the string)
- 5950: Return from INT2STR (exit)
- 5960: Label-Start of ITSTABLE (powers of 10)
- 5965-5980: Powers of 10 for digit positions
Done
Next time I will go over the library I have built so far. Then I will put it to use on something fun!
Pingback: 6502, Atari 8 bit Library | Unfinished Bitness
I just wanted to say thanks for this. I am doing a deep dive into 6502 assembly for the first time in a long time, and this helps – a lot.
Your welcome. Glad you’re finding it useful getting started.