8-Bit Software

The BBC and Master Computer Public Domain Library

Back to 8BS Or Back to Disassembly


BASIC ROM Routines
Page last modified:


MENU:   Introduction/News   Routines listed in
 Address order
  Routines listed
 by Category
  BASIC ROM documentation
 by Christopher Dewhurst
  BASIC4r32





BASIC ROM ROUTINES
==================
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
STA &2A
LDA #integer 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 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
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!

Christopher Dewhurst
24 September, 2000



 Back to 8BS
Or