BASIC ROM Routines
Routines listed in Address order
Routines listed by Category
BASIC ROM documentation by Christopher Dewhurst
The Basic Rom is that dark and mysterious area of memory that lies beyond the screen, starting at &8000 and stretching up to &BFFF. It's the backstage department containing the machine code needed to interpret your Basic commands. But have you ever wondered if we can use some of that machine code in our own programs? Have you, for instance, struggled to write an assembler routine to print a number on the screen, when one must surely exist somewhere in the Basic Rom?
Well, wonder no more, because there is indeed such a routine, and in this article we'll be exploring that and a lot more besides.
Before we go any further, however, a word of caution. The best machine code is specific machine code written for a specific job; Basic Rom routines are general-purpose routines, and are not the answer to everything. Having said that, if speed is not your main priority, then the Rom routines are ideal. They make your programs smaller and smarter, provided you use them properly - and this usually involves some fairly tricky setting up - so listen carefully.
I learnt a lot about the Basic Rom by exploring around it and experimenting with it myself. I also picked up a few tips from 'The Advanced Basic Rom User Guide' by Colin Pharo (Cambridge Micro Centre, 1984). Roland Waddilove also presented a series of excellent articles on the subject in 'Electron User'; if you still have these paper beauties, dig out the November 1988 for a rundown on mathematical Rom routines. However, I will be concentrating on routines which print numbers in hex or decimal, the random number generator, and printing strings of text.
In case you're wondering, BBC Master owners won't be left out of the discussion this time. I have done quite a bit of disassembling of the Basic 4 Rom to find out where equivalent routines to Basic 2 reside. Basic 2 is the Rom fitted in the BBC B and Electron, and Basic 4 is the one fitted in the Master. (Like the Plus 2, for some reason Basic 3 never was.)
When I talk about a Rom routine, I will specify both the Basic 2 and Basic 4 addresses - together with examples and commentaries on how to use them - so it is up to you to use the correct one depending on which computer you have.
If you experience any difficulties - or if you have additional hints and tips - just email me.
Right, down to business. We must first get to know what is called the Integer Work Area, or IWA for short. This is just a sequence of four bytes in zero page, located at &2A-&2D. Before Basic can work on an integer variable, be it adding a number to it or printing it out, it must be put into the IWA. Fortunately, life is made easier with the help of a couple of routines which copy an integer variable, either from zero page or from the main memory, to the IWA:
1. Routine: Copy 4-byte integer from zero page to the IWA
Basic 2 address: &AF56
Basic 4 address: &AA80
Entry: X = zero page offset at which the integer to be copied is located.
Exit: IWA contains the integer.
Ex.: LDX #&70 \integer at &70-3
JSR &AF56 \copy to IWA
2. Routine: Copy 4-byte integer from memory to the IWA
Basic 2: &B336
Basic 4: &B1AA
Entry: &2A/&2B contain address of the integer.
Exit: IWA contains the integer.
Ex.: LDA #integer MOD 256
LDA #integer DIV 256
.integer EQUD &12345678
There are also two routines which do the opposite of above. The one at &BE44 (Basic 2)/&BDC6 (Basic 4) copies the IWA to a zero-page location, X being set to the zero page location on entry. The routine at &B4C6 (Basic 2)/ &B347 (Basic 4) copies the IWA to a location in main memory whose address is held in &37/&38.
3. Routine: Print a string
Basic 2: &BFCF
Basic 4: &BECF
Entry: The string must follow the JSR &BFCF instruction, and be terminated
by a byte of value &80 or greater.
Ex.: JSR &BFCF
EQUS "Hello there.":NOP
Notice how I've used the NOP instruction to terminate the string. The NOP opcode has a value of &EA, which satisfies the condition of being &80 or greater. The important point to remember is that program execution continues AFTER that NOP instruction. In machine code, every time a JSR instruction is executed the current address is pushed onto the stack. Basic pulls this address from the stack, stores it in zero page locations &37/&38, and uses indirect addressing to get the bytes of the string. By the time the string has been printed, &37/&38 contains the address of the next instruction after the string in the program that called the routine. The disadvantage of this routine, however, is that while you can include control codes (to turn off the cursor for instance) you can't print out a string of graphics characters because they have an ASCII value of &80 or above which, as we said, is used to terminate the string.
4. Routine: Print A in hex
Basic 2: &B545
Basic 4: &BD6C
Entry: The Accumulator contains the byte to be printed in hex
Ex.: LDA #&CD
This one can be quickly demonstrated from Basic, if you really want, by typing A%=&CD:CALL&B545.
5. Routine: Print 16-bit number in decimal
Basic 2: &991F
Basic 4: &A081
Entry: &2B/&2C (the 2 least significant bytes of the IWA) should contain
the number to be printed.
Ex.: LDA #1023 MOD 256 \put the number 1023
LDA #1023 DIV 256 \onto the two lsb's of the IWA
This is the routine which I promised we would discuss at the beginning of this article, so let's take some time going through it in detail. If you have a disassembler then you could look at the actual machine code, which in English goes something like this. You first of all see how many times 10,000 can be subtracted from the given number before it becomes negative. For example, you can subtract 10,000 six times from the number 60,000. This is the 10,000s count. Then you see how many times 1,000 can be subtracted from the remainder, then how many times 100 can be taken away from the remainder of that, and so on down to the 1s count. In order to do this, we need a table of two-byte values: 10,000, 1,000, 100, 10 and 1. There are two tables in Rom; the first table contains the low bytes, and the second table contains the high bytes:
Basic 2 Low bytes: High bytes
&996B [&01] &99B9 [&00] &0001 = 1
&996C [&0A] &99BA [&00] &000A = 10
&996D [&64] &99BB [&00] &0064 = 100
&996E [&E8] &99BC [&03] &03E8 = 1000
&996F [&10] &99BD [&27] &2710 =
Basic 4 Low bytes High bytes
&8026 [&01] &8021 [&00] &0001 = 1
&8027 [&0A] &8022 [&00] &000A = 10
&8028 [&64] &8023 [&00] &0064 = 100
&8029 [&E8] &8024 [&03] &03E8 = 1000
&802A [&10] &8025 [&27] &2710 = 10000
If you haven't got a disassembler, then the program below demonstrates how it works:
40 LDX #&50 \copy integer from zero page
50 JSR &AF56 \to IWA
70 LDX #4
80 .loop LDA#0
90 STA &3F,X
110 .loop2 LDA&2A
120 SBC &996B,X \&8026 for BBC M
140 LDA &2B
150 SBC &99B9,X \&8021 for Basic 4
160 BCC skip
170 STA &2B
180 STY &2A
190 INC &3F,X
200 BNE loop2
210 .skip DEX
220 BPL loop
280 LDX #5 \suppress leading zeroes
290 .loop3 DEX \by indexing to first
300 BEQ print \non-zero number
310 LDA &3F,X
320 BEQ loop3
330 .print LDA &3F,X
340 ORA #&30
350 JSR &FFEE
370 BPL print
410 INPUT !&50
420 CALL &900
The section of code from line 280 to 320 suppresses leading zeroes. This just means that if you had the number 234, then it will be printed as 234 and not 00234. Sometimes you might not care for leading zero suppression. In most games, for instance, your score is displayed as 00000 at the start, then changes to 00010 when you score some points and so on. In this case, you can dispense with lines 290-320 in the above program and replace line 280 with LDX #4.
6. Routine: Convert number in IWA to ASCII decimal or hexadecimal
Basic 2: &9EFF
Basic 4: &A138
Entry: IWA should contain number to be converted location &15 = &FF for hexadecimal ASCII or &15 = 0 for decimal ASCII
The previous routine only allowed 16-bit numbers (numbers in the range 0-65535) to be printed. This routine helps you print 32-bit numbers in decimal or hex. When we speak of "hexadecimal" or "decimal ASCII", it means that a string containing ASCII codes is made for the given number. For example, the four ASCII decimal codes for &9EFF are 57, 69, 70, and 70 (ignoring the ampersand which Basic doesn't print anyway). Basic puts these ASCII codes into the String Work Area, or SWA for short. We can then use another routine to print out the contents of the SWA:
7. Routine: Print the SWA
Basic 2: &8E12
Basic 4: &921B
Entry: location &30 must contain the length of the string the SWA must contain the string location &A must = 0.
Ex.: LDX #&50 \copy integer at &50-3
JSR &AF56 \to SWA
LDA #&FF:STA &15 \number to be in hex
JSR &9EFF \convert IWA to ASCII codes
LDA #0:STA &A
JMP &8E12 \and print the number
8. Routine: Random number generator
Basic 2: &AF51
Basic 4: &AA7B
No entry requirements. On exit, the IWA contains a 4-byte random integer.
Ex.: JSR &AF51
I find this Rom routine extremely useful for getting random numbers in games, and it's the only decent way of getting fairly unpredictable numbers in machine code.
If you use any of the above routines, don't forget to use the correct address for your version of Basic. Now that you've seen what the Basic Rom can do, hopefully you'll want to start exploring other parts. If you find anything useful, do write in and let us know!
24 September, 2000