The Basic ROM
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 issue 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 write in.
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 #Intergalactic_Traderger MOD 256 STA &2A LDA #Intergalactic_Traderger DIV 256 STA &2B JSR &B336 ... .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 JSR &B545
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 STA &2B LDA #1023 DIV 256 \onto the two lsb's of the IWA STA &2C JSR &991F
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 = 10000 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:
10 FORI%=0TO2STEP2 20 P%=&900 30 [OPTI% 40 LDX #&50 \copy integer from zero page 50 JSR &AF56 \to IWA 60 70 LDX #4 80 .loop LDA#0 90 STA &3F,X 100 SEC 110 .loop2 LDA&2A 120 SBC &996B,X \&8026 for BBC M 130 TAY 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 270 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 360 DEX 370 BPL print 380 RTS 390 ] 400 NEXT 410 INPUT !&50 420 CALL &900
The section of code from line 280 to 320 suppresses leading zeros. 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 LDA &2A JSR alien
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.
Conclusion
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!
Chris Dewhurst, EUG #55