Basic ROM Routines

By Chris Dewhurst

Originally published in EUG #55

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