ROM calls and other tricks

Basic and Machine Language

Moderator: Moderators

carlsson
Class of '6502
Posts: 5516
Joined: Wed Mar 10, 2004 1:41 am

ROM calls and other tricks

Post by carlsson »

As Leif pointed out, there are more or less annotated ROM listings on the Internet and in various books. Each listing tends to use different labels, entry points and vary in detail information.

Here are a collection of (what I've found) particulary useful ROM calls to use. Rather than sorting them memory-wise, I tried to arrange them after what they can be used for. All addresses are of course for VIC-20, but most of them could even be used on the C64 by subtracting $2000 for addresses < $E000. In a few cases, the two ROMs are off by a few more bytes though.

First, a few zero page locations used further down:

INDEX1: $0022-$0023, First utility pointer
INDEX2: $0024-$0025, Second utility pointer
FORNAM: $0049-$004A, Pointer to FOR/NEXT index variable etc
TEMPF3: $004E-$0052, Temporary FLPT storage
TEMPF1: $0057-$005B, Temporary FLPT storage
TEMPF2: $005C-$0060: Temporary FLPT storage
FAC: $0061-$0066, Floating-point Accumulator (FAC)
AFAC: $0069-$006E, Alternative/Auxilary FAC

Basic does most of its calculations in floating-point (referred to as FLPT or MFLPT below), as you probably know. Therefore, a number of interesting routines working with floating-point numbers are stored in Basic ROM.

Notice that the ROM routines will use these, and possibly more zero page addreses, so if you use the zero page for own things, carefully disassemble or experiment which addresses are not touched. If you are not using floating point at all in your program, I would say that all these addresses are available for your own usage.

The syntax (A/Y) means the accumulator should contain the low byte, and the Y register contains the high byte.

Routines for execution of a Basic program
$C533: Rebuild Basic text link pointers, as discussed in the VIC utility cartridge thread. This would rarely be used unless you are moving a Basic program within memory (or make an OLD command or similar).
$C659: Reset execution to start of program and perform CLR (set pointers to variables).
$C7AE: Handle next Basic statement from current position. Not really the entry point for RUN ($C871), but it works..

Routines for SYS parameter passing
$CEFD: Read comma from BASIC text. If another symbol is found, ?SYNTAX ERROR. There are sister routines: $CEF7 checks right parenthesis, $CEFA checks left parenthesis, $CEFF compares vs accumulator (i.e. any character).
$D08B: Find variable from BASIC text. Returns address to variable in A/Y.
$D79B: Evaluate expression from BASIC text. Returns integer 0-255 in X register. Call $D79E if text pointer has already advanced.
$DBD0: Store FAC in variable pointed to by FORNAM ($49/4A).

Routines for the jiffy clock
$C9E3: Set TI$ from string. Store the address to the string into INDEX1 (low/hi) and load the accumulator with 6 (the string length). Not so useful maybe, but nice to know. If the string contains non-numeric values, you get ?ILLEGAL QUANTITY just like you would get from Basic. Notice that there are Kernel calls both to read and set the clock numerically.
$CF48: Convert TI to ASCII string and set (FAC+3) to point to string. This is useful in combination with $CB21 below to PRINT TI$.

Routines for printing
$CB1E: Print string pointed to by (A/Y) until zero byte.
$CB21: Print string pointed to by (FAC+3) until zero byte.
$CB24: Print string pointed to by (INDEX1) of length (A).
$DDCD: Print integer in (X/A). This is actually part of the routine that prints the current line number, stored in $0039-$003A.
$DDD7: Print (FAC) as ASCII string.

Routines for converting from/to FLPT/FAC
$D391: Convert integer in (A/Y) to FLPT in (FAC) within range 0 to 32767.
$D3A2: Convert (Y) to FLPT in (FAC) within range 0 to 255. These two routines are common entry points if we want to take advantage of the FAC.
$D7B5: Convert string starting at (INDEX1) of length (A) to FLPT value in (FAC). Could be useful if you have floating point constants in text format?
$D1AA: Convert (FAC) to integer in (A/Y).
$D7F7: Convert FAC to integer in (INDEX1) in range 0 to 65535.
$DDDD: Convert FAC to ASCII string starting at STACK ($0100) and ending with null byte. It corrupts $00FF. It can be useful if you want the string in memory but not print it on screen.

Routines for loading and storing FAC
$DA8C: Load AFAC with MFLPT pointed to by (A/Y).
$DBA2: Load FAC with MFLPT pointed to by (A/Y).
$DBC7: Store (FAC) into TEMPF2.
$DBCA: Store (FAC) into TEMPF1.
$DBD4: Store (FAC) into location pointed to by (X/Y).
$DBFC: Load (FAC) from (AFAC).
$DC0C: Load (AFAC) from (FAC).

Arithmetic FAC routines
$D850: Subtraction: (FAC) = MFLPT value at (A/Y) - (FAC). It calls $DA8C, so entering at $D853 would mean (FAC) = (AFAC) - (FAC).
$D867: Addition: (FAC) = MFLPT value at (A/Y) + (FAC). Ditto call as for subtraction.
$DA28: Multiply (FAC) by MFLPT value at (A/Y), answer in (FAC). It also calls $DA8C, checks whether the multiplier is zero and then continues at $DA30.
$DAE2: Multiply (FAC) by 10.
$DAFE: Divide (FAC) by 10.
$DB07: Divide (AFAC) by MFLPT value at (A/Y), sign in (X), answer to (FAC).
$DC2B: Find sign of (FAC), result in A: $01 = positive, $00 = zero, $FF = negative.
$DC58: Perform ABS function. It actually only shifts down the FAC sign ($0066) by one bit, so it becomes a positive value..
$DC5B: Compare (FAC) with MFLPT value at (A/Y): $01 = (FAC) > MFLPT, $00 = equal, $FF = (FAC) < MFLPT.
$DCCC: Perform INT function, convert (FAC) to integer and back to FLPT format again.

Various Basic calls
$E094: Perform RND. To get RND(1) functionality, enter at $E0BB. It will leave its value in (FAC).

Kernal calls - these may differ between VIC and 64
$E518: Initalize input/output. Pretty much what happens to the screen when you press RUN/STOP + RESTORE.
$E581: Home cursor, reset screen line link table.

Oh dear, this is a long list, and it is nowhere a complete disassembly. Most of it deals with how to juggle and use the FAC. While you probably can do integer arithmetic anyway, complex multiplication or use of other Basic routines (SQR [$DF71], EXP [$DFED], SIN [$E268], ATN [$E30B] etc) will be more handy - although maybe not execution time efficient - than reinventing the wheel.

I didn't even list the Kernel vector table ($FF8A to $FFF3), as it should be fairly documented elsewhere.
Last edited by carlsson on Tue Mar 14, 2006 5:11 pm, edited 1 time in total.
Anders Carlsson

Image Image Image Image Image
aneurysm
not your PAL
Posts: 178
Joined: Sat Mar 06, 2004 11:06 pm

Post by aneurysm »

so if you plan on using absolutely no Basic routines how many zero page addresses are opened up?
carlsson
Class of '6502
Posts: 5516
Joined: Wed Mar 10, 2004 1:41 am

Post by carlsson »

Err.. not sure. For my latest game (Jewels 20), I used 28 zero page locations, which is a new personal record: $4E-$52, $57-$60 (minus $5D-$5E), $A7-$AB, $F7-$FF. Some of these addresses will be reused by the ROM routines I call, but as long as I only use them for temporary stuff and not need them after the call, it is ok.

I've seen some (C64) programs that use a few hundred, depending on whether you want to return to Basic afterwards or not. I suppose $00-$02 also are "available" on the VIC-20, and possibly many more like $22-$25, $61-66, $69-$6E and so on.
Anders Carlsson

Image Image Image Image Image
aneurysm
not your PAL
Posts: 178
Joined: Sat Mar 06, 2004 11:06 pm

Post by aneurysm »

I was using $2D-$34. But looking in the book now I see if those are fair play for what I'm doing I could go $2D-$4A w/o problems. And even more...
carlsson
Class of '6502
Posts: 5516
Joined: Wed Mar 10, 2004 1:41 am

Post by carlsson »

Another interesting ROM call, learned from comp.sys.cbm:

$D08B (53887): Find a variable, return indirect address in A/Y

"Indirect" means it is a handle to the variable which tells its size (1 byte) and location (2 bytes) in memory. Basic calls the routine above from $CF28 (53032) and stores the address on the FAC mantissa ($64/65, dec. 100/101).

This extended example is inspired by Ryan Sherwood's demonstration program. Notice that there is no comma after the SYS, since the Basic routine does not expect one:

Code: Select all

10 D$="HELLO":F$="LOPEZ"
15 PRINT"D$=";D$;" AND F$=";F$
20 SYS 53387D$:V1=1+PEEK(780)+PEEK(781)*256
25 SYS 53387F$:V2=1+PEEK(780)+PEEK(781)*256
30 VL=PEEK(V1):VH=PEEK(V1+1):POKE VL+VH*256,74
35 POKE V1,PEEK(V2):POKE V1+1,PEEK(V2+1)
40 POKE V2,VL:POKE V2+1,VH
45 PRINT"D$=";D$;" AND F$=";F$
This program both changes the content of one of the strings (line 30) and swaps the two strings (lines 35-40) in memory. The strings in this program are static, but it should work for dynamically created strings too, as well as for integers and floating point numbers etc.

I think someone (Boray?) asked a question how to pick up a string value in machine code and got a workaround answer. This information was new to me an hour ago, and I'm sure it could have some use to you too!

By the way: Don't use string variable E$ in the SYS above. You'll have to figure out yourselves why SYS 53387E$ will make Basic say ?SYNTAX ERROR. In the newsgroup, Ian and Cameron both wrote a small ML routine which would make the ROM call instead of calling it directly. By doing that, one can get around the E$ bug.
Anders Carlsson

Image Image Image Image Image
User avatar
Mike
Herr VC
Posts: 4816
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Post by Mike »

SYS(53387)E$ also works. Here 53387 is included in parenthesis so E can't be mistaken as ... ;)

Michael
User avatar
Mike
Herr VC
Posts: 4816
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Post by Mike »

Small routines for saving and loading blocks of memory:

SAVE memory block:

Code: Select all

SYS57809(N$),8,1:POKE193,start_lo:POKE194,start_hi
POKE780,193:POKE781,end_lo:POKE782,end_hi:SYS65496
'start' is inclusive, 'end' is exclusive. 'N$' contains the file name. Other device numbers than 8 can be used (also 1 for tape), but the additional ',1' must be kept.

LOAD memory block:

Code: Select all

SYS57809(N$),8,1:POKE780,0:SYS65493
Loads the file 'N$' to its correct address without restarting the BASIC program.

Michael

Edit: Removed unnecessary POKE147,0 instruction in LOAD memory block.
Last edited by Mike on Fri Mar 13, 2009 5:08 pm, edited 1 time in total.
carlsson
Class of '6502
Posts: 5516
Joined: Wed Mar 10, 2004 1:41 am

Post by carlsson »

Cool. Although I assume everyone who have ventured into machine code programming already knows, the addresses 780-783 transport the values of accumulator (A), X and Y registers and finally the status (flags) register. It is common parameter passing from Basic to ML routines.
Anders Carlsson

Image Image Image Image Image
carlsson
Class of '6502
Posts: 5516
Joined: Wed Mar 10, 2004 1:41 am

Post by carlsson »

On the topic of loading, here are another few calls that are not useful for programming, but for .. eh, getting the most out of tape files, if you see what I mean.

SYS 63407 = Reads from tape until the header of a program is found
SYS 62980 = Reads the rest of the program

It means after the first call, one can modify the contents of the tape buffer to make the program load into another address (prevent auto start etc) and then check what the program does.

I found these calls from a computer magazine in 1990, telling its readers how to hack C64 tapes (or rather, "find hidden messages"). On the C64, the SYSes are 63276 and 62828, so slightly different position than on the VIC.
Anders Carlsson

Image Image Image Image Image
carlsson
Class of '6502
Posts: 5516
Joined: Wed Mar 10, 2004 1:41 am

Post by carlsson »

Hm. For some of these calls marked by (A/Y), it seems A contains the high byte and Y the low byte, while it should be the opposite according to my source. This applies in particular to $D391, but possibly to many other routines. I never observed this before, but it is worth checking if a call doesn't give the expected result to change order of the parameters.
Anders Carlsson

Image Image Image Image Image
User avatar
Mike
Herr VC
Posts: 4816
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Recovering from a tape ?LOAD ERROR

Post by Mike »

Often enough it happened to me, that I stripped off the end of a program saved on tape, because I started the SAVE too early on the tape. This destroys the copy, but the original is often untouched. Still, it causes a ?LOAD ERROR. You then can't RUN the program, because the pointers (45/46) haven't been set.

You can recover these pointers (and your program, and your state of mind) in that case with:

Code: Select all

POKE 45,PEEK(174):POKE 46,PEEK(175):CLR
Michael

P.S.: You can use VERIFY to search for the end of a program on tape.
User avatar
Schlowski
NoMess!
Posts: 892
Joined: Tue Jun 08, 2004 12:20 pm

Post by Schlowski »

Is there any (easy) possibility to save a certain area of RAM with another load address?
Saving $1000-$1800 with load address $1C00 for example.
Boray
Musical Smurf
Posts: 4064
Joined: Mon May 03, 2004 10:47 am

Post by Boray »

Schlowski wrote:Is there any (easy) possibility to save a certain area of RAM with another load address?
Saving $1000-$1800 with load address $1C00 for example.
You should be able to just open a sequencial file, save two address bytes and then save the rest of the data. Also check this: http://sleepingelephant.com/ipw-web/bul ... php?t=1270
PRG Starter - a VICE helper / Vic Software (Boray Gammon, SD2IEC music player, Vic Disk Menu, Tribbles, Mega Omega, How Many 8K etc.)
User avatar
Schlowski
NoMess!
Posts: 892
Joined: Tue Jun 08, 2004 12:20 pm

Post by Schlowski »

Ah, I knew there was something like that and I read it, but I couldn't find it anymore.
Thanks, I will have a look at it and try it.
User avatar
Mike
Herr VC
Posts: 4816
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

BASIC auto-tokenizer for SEQ files

Post by Mike »

If the listing in charge doesn't have any control-characters inside (as it's the case here) you can 'type in' the listing as follows, provided you have VICE available:

1. Copy and paste the listing from your browser into your favorite editor. Make sure it saves with carriage returns (i.e. ASCII 13).
2. Save it to the VICE main directory, i.e. as 'text.txt'
3. Start xvic, and drag the file into the VICE main window.

This needs some explanation: the file is still ASCII, and will not work. But dragging the file into the main window is the easiest way to mount the surrounding directory as drive.

4. Do a hard-reset (Ctrl-Alt-R).
5. Type in this program (you should save it for later uses):

Code: Select all

63996 POKE812,238:OPEN2,8,2,"TEXT.TXT,P,R"
63997 PRINT"{CLR}";:IFST<>0THENPOKE812,239:CLOSE2:END
63998 GET#2,A$:PRINTA$;:IFA$<>CHR$(13)THEN63998
63999 PRINT"GOTO63997":POKE631,19:POKE632,13:POKE633,13:POKE198,3
6. RUN this program. It will type in 'TEXT.TXT' for you.
7. delete lines 63996 to 63999.
8. done. You can now mount a *.d64 file as usual, and save the program as real *.prg file.

One point to note are the POKE812,x commands. They disable the CLALL vector at the start, and re-enable it, when the program finishes. Otherwise the channel would be closed upon entering a new line.

The tokenizer (of course) also works with SEQ files. In that case the ',P,R' inside the OPEN commands must be replaced with ',S,R'. Finally, it can be used to reverse the effect of PETSCII LIST output to a file with:

Code: Select all

OPEN2,8,2,"TEXT,S,W":CMD2:LIST
PRINT#2:CLOSE2
except that the final 'READY.' will either lead to an '?OUT OF DATA' or '?SYNTAX' error. Just type in another 'GOTO63997' + RETURN to finish the process correctly (i.e. with restoring the CLALL vector, and closing channel #2).

Michael
Last edited by Mike on Tue Jan 13, 2009 1:33 am, edited 2 times in total.
Post Reply