A&B COMPUTING JANUARY 1990, p91-93
"Software Support" section
XBASIC
Add new structures and utilities to your BASIC ROM
David Bower
•Model B/B+
•Master series
•Electron
•BASIC 2 required
When Acorn launched the BBC Micro in 1982, it was widely recognised as the best personal computer on the market. The Model B and the later Master 128 are still widely used in homes and schools. Much of the credit for this success belongs to the software - the operating system and BASIC - that is supplied with the machines.
For serious users, the feel of a computer is determined by the programming language and the collection of editors, filing systems, utilities and debugging tools which create the programming environment. BBC languages include BCPL, C, COMAL, FORTH, LISP, LOGO, PASCAL and Micro-PROLOG. They all have particular strengths and some fanatical followers.
However, the great majority of BBC programmers - plus some Macintosh, Amiga, Nimbus and IBM PC owners - write in BBC BASIC and enjoy its features:
• Reasonable control structures to ease program design (FOR...NEXT and REPEAT....UNTIL loops and IF....ELSE)
• PROCedures with parameter passing and local variables
• Arbitrary length variable names
• Fast program execution for its time
• Accurate floating-point arithmetic and functions
• Easy handling of graphics, sound and peripheral ports
• Built-in assembler for time-critical routines
• Last, and by no means least, BBC BASIC has proved robust and relatively error-free for such a complex software product.
Roger Wilson of Acorn, the man behind all the versions of the BASIC interpreter, wrote BASIC I in record time to meet a BBC imposed deadline. In 1983, BASIC 2 corrected (almost!) all the bugs and added the OSCLI and OPENUP keywords. BASIC 4 (Master 128) and BASIC 40 (Master Compact) exploit extra instructions on the 65C12 micro-processor to improve floating-point operations, but the language is essentially identical.
BBC BASIC is an amazing achievement for 16 Kbytes of machine code, but it is capable of improvement. Many of you will be aware that Archimedes BASIC has improved program structures and debugging capabilities - but you would expect no less with half a megabyte of ROM available for RISC-OS, BASIC and applications.
When I first considered modifying BASIC, the Archimedes was far over the horizon. However, I spend much of my professional life doing scientific programming on Hewlett-Packard workstations which make the Archimedes look rather sluggish. The 'Rocky Mountain' BASIC on these machines includes almost every conceivable facility - the technical manuals take up over two feet of shelf space - and it provided an initial list of desirable features.
To get started, I needed a lot of detailed information about BASIC and I was lucky enough to discover a copy of 'The BBC Micro Compendium' in a local library. Acorn banned this book when they found it included a commented dis-assembly of the BASIC 2 ROM. However, a few copies leaked into circulation.
The primary objectives of XBASIC were as follows:
(1) Add extra programming structures to BASIC to simplify program design and maintainance:
• Multi-line IF...ELSE...IF blocks
• WHILE.... WEND loops
• ON <variable> PROC statements (as BASIC 4/40)
• GOTO, GOSUB and RESTORE to program labels
(2) Add useful functions such as MAX and MIN
(3) Speed up the floating point operations of BASIC
(4) Include extra debugging tools and utilities:
• Improve the format of program listings with correct structure-indentation and optional splitting of multi-statement lines into individual lines
• Search a program for a selected string
• No screen corruption and a single-step option on TRACE
• List/verify a program direct from disc
• List program variables
(5) XBASIC should run any BBC BASIC program correctly and be completely compatible with all BASIC versions and all BBC machines (B/B+/Master 128/Master Compact/Electron)
The XBASIC ROM that now sits in my Master 128 - two years and hundreds of programming hours later - contains all these features and many more. So how was it done?
The obvious approach would be to rewrite BASIC more compactly to leave space for extra features, but Roger Wilson's machine code is written with a tautness and elegance which leaves very little slack.
I briefly toyed with the idea of writing a new interpreter which omitted some less-used facilities. This would have been a massive task, but it was the method chosen by the authors of BBC COMAL. Although COMAL is certainly well-structured, I believe the sacrifices involved - especially the lack of compatibility with BASIC programs and the loss of the built-in assembler - are too great to tolerate.
The next option I analysed required the use of a 6502 second processor. If you run BASIC on these machines, there is a big gap in the memory map, from &C000 upwards, where the extra code could be in-serted. Colin Dean exploited this technique when he wrote Advanced BASIC for Tubelink to emulate BASIC 5 on the Archimedes. If you wish to be compatible with BASIC 5 (down to the level of keyword tokenisation) and own a second processor look no further - the product works very well. However, the vast majority of BBC users don't have a second processor and l wanted to add keywords that don't exist on the Archimedes.
The fourth technique I considered is discussed in the 'BASIC ROM User Guide' by Mark Plumbley. When the BASIC interpreter cannot make any sense of a statement, it signals an error by executing a BRK instruction. The operating system then transfers control to an address contained in the BRK vector. This normally points to an error handler set up by BASIC, but control can be redirected to code in another ROM.
This code must first decide if the statement corresponds to a extra facility, then execute appropriate instructions and finally jump back into BASIC. The overall effect is that BASIC is normally in control, but occasionally another ROM seizes the reins.
Although relatively simple in principle, the practical problems are immense. Errors can have all sorts of causes which can only be analysed by examining the state of the 6502 stack and the BASIC program pointers. I decided that the method was unreliable - machine 'crashes' would result from incorrect analysis - and in any case it would slow down program execution drastically. The technique I finally implemented reverses the above procedure. The XBASIC ROM is normally in control and can recognise all BASIC commands plus the XBASIC extensions. When it wishes to perform selected routines, it transfers control to BASIC. After the routine is executed, control returns automatically to XBASIC. As the transfer of control takes place in a well-defined fashion, this procedure avoids the hazards and high overheads of the BRK intercept method.
I had better describe this technique in more detail. I started off by searching the BASIC 2 ROM for large blocks of self-contained code which take several milliseconds to execute. These blocks must terminate with an RTS instruction or an error rather than JMPing back to the infinite loop which is the core of the interpreter. I identified nine such blocks - a typical example is the routine which deletes lines from a program - totalling about 4 Kbytes. Deleting these blocks from the ROM provides the space for the machine code which handles the new facilities. This procedure has the huge advantage that the bulk of XBASIC consists of efficient, debugged BASIC 2 code but leaves three major problems to be overcome:
(1) How to recognise the new XBASIC commands.
(2) How to access the BASIC routines deleted from XBASIC.
(3) How to fit the extra XBASIC features in the available space. BASIC is normally limited to a maximum of 128 keywords which are replaced by single-byte tokens on program entry. This reduces the storage space needed for a program and allows efficient analysis and execution of BASIC statements.
To extend the command set, I borrowed an Archimedes technique and used double-token combinations where the first token is either &C6 (for functions) or &C7 (direct commands) or &C8 (program statements). These particular tokens are illegal inside a BASIC program, so there is no possibility of confusion. I had to extend and modify several routines to make double tokenisation work:
(1) The tokeniser routine itself
(2) The keyword table which lists each command together with its token and a flag byte.
(3) The action-address table which tells the interpreter the address of the code which executes each command.
(4) The routine which converts a token back into a keyword. Jumping between ROMs is a delicate affair. Acorn suggest using extended vectors - see the Advanced User Guide for details. This is quite slow and messy, so l adopted a different technique. Every time XBASIC runs a pro-gram, it first finds the rom-slot and version of BASIC and writes a small machine-code patch into its private language workspace in RAM. Whenever it needs to access a BASIC routine, it reads the entry-point address - which varies in each version of BASIC - from a data-table, pokes that address into the patch and executes a JSR to the start of the patch. The patch switches control to BASIC by altering the ROM-select switch at &FE30 and the RAM copy at &F4. It then executes a JSR to the poked address.
When the routine finishes with an RTS, control returns to the patch. The patch switches ROM-selection back to XBASIC and finally executes an RTS to return to the original calling location. This technique is flexible and the overheads are only about 45 microseconds, so performance is only slightly degraded for routines taking several milliseconds. The only drawback is that it is not second-processor compatible. If an error occurs inside the BASIC routine, the operating system automatically transfers control to the XBASIC error-handler.
Code space was reduced by successive refinement of routines and by making several routines multi-purpose. It would have been easier if I had exploited the 65C12 instruction set, but that would have ruled out compatibility with Model B and B+. The majority of the routines are relatively straightforward and most of my time was spent ensuring that they interfaced correctly with the original code and previous modifications. Each new feature was checked in isolation and I used a suite of BASIC programs, hopefully testing most of the ROM, to search for more subtle bugs. The most difficult part of the entire exercise was the attempt to improve floating-point speed. The run-time of many computation intensive programs is dominated by multiply and divide opera-tions. A first re-write of these routines produced an 8% speed-up. I next modified the SQR routine. BASIC2 takes a crude initial estimate and applies five successive iterations of a refinement procedure called the Newton algorithm. I found a very quick way to get an improved initial estimate which only needs three iterations to achieve the same accuracy. When BASIC 40 appeared on the Master Compact, it used faster and more accurate algorithms for all floating point operations. Most of these were too lengthy to be usable in XBASIC, but I did adapt the multiply and divide routines for a 20% speed-up over BASIC 2. This change and the SQR improvement have a knock-on effect on the computation of transcendental functions (eg SIN and EXP) which are speeded up by between 25% and 35%.
The final floating point modification was to the TAN function. (Non-mathematicians should skip the next three sentences. BASIC 2 computes TAN as SIN/COS which is formally correct but rather slow. BASIC IV uses a continued-fraction method for generating transcendentals which is efficient and has much better global accuracy than power series methods. I took a minimax rational approximation for TAN and converted it into continued-fraction form. TAN now runs three times faster than its BASIC 2 equivalent and is slightly more accurate for some ranges of its argument.
If I had realised how much time this project would eventually absorb I might never have started. Now that it is completed, I can start thinking seriously about an Archimedes ... but that is another story. Model B/B+ and Electron owners who are not thinking about an Archimedes might like to turn to the advert in this issue to find out how to obtain a copy of XBASIC. The following listing does not require XBASIC to be installed in your machine for it to work. It comprises two benchmarks which can be run both before & after installation of the XBASIC rom for comparsison. It also demonstrates the advanced LISTO option.
BEGIN LISTING:
232 REM Program Benchmk v1 (BASIC)
1010 REM Savage Floating-point Benchmark
1020 REM Ackermann Recursion Benchmark
1030 REM (C) D.E. Bower June 1989
1040
1050 MODE 7
1060 FOR row=1 TO 2
1070 PRINT TAB(5, row)CHR$130;CHR$141;"Benchmark Programs"
1080 NEXT
1090 PRINT TAB(10,24)CHR$136;CHR$129;"Calculating";
1100
1110 PRINT TAB(0,6); "Savage Floating-point Benchmark"
1120 @%=&A0A:A=1:TIME=0
1140 FOR J%=1 TO 2499
1150 A=TAN(ATN(EXP(LN(SQR(A*A)))))+1
1160 NEXT
1170 PRINT " Result = ";A
1180 PRINT " Time = ";TIME/100; " secs"
1190
1200 PRINT TAB(0,14);"Ackermann Recursion Benchmark"
1210 M%=3
1220 FOR N%=1 TO 4
1230 TIME=0
1240 Z%=FNAck(M%,N%)
1250 PRINT " Ack (";STR$(M%);",";STR$(N%);") = ";Z%;
1260 PRINT TAB(16);"Time = ";TIME/100;" secs"
1270 NEXT
1280 PRINT TAB(10,24);STRING$(15," ");TAB(0,24);
1290 END
1300
1310 DEF FNAck(M%, N%)
1320 IF M%=0 =N%+1
1330 IF N%=0 =FNAck(M%-1,1)
1340 =FNAck(M%-1,FNAck(M%, N%-1))
END LISTING AT LINE 1340
"Software Support" section
XBASIC
Add new structures and utilities to your BASIC ROM
David Bower
•Model B/B+
•Master series
•Electron
•BASIC 2 required
When Acorn launched the BBC Micro in 1982, it was widely recognised as the best personal computer on the market. The Model B and the later Master 128 are still widely used in homes and schools. Much of the credit for this success belongs to the software - the operating system and BASIC - that is supplied with the machines.
For serious users, the feel of a computer is determined by the programming language and the collection of editors, filing systems, utilities and debugging tools which create the programming environment. BBC languages include BCPL, C, COMAL, FORTH, LISP, LOGO, PASCAL and Micro-PROLOG. They all have particular strengths and some fanatical followers.
However, the great majority of BBC programmers - plus some Macintosh, Amiga, Nimbus and IBM PC owners - write in BBC BASIC and enjoy its features:
• Reasonable control structures to ease program design (FOR...NEXT and REPEAT....UNTIL loops and IF....ELSE)
• PROCedures with parameter passing and local variables
• Arbitrary length variable names
• Fast program execution for its time
• Accurate floating-point arithmetic and functions
• Easy handling of graphics, sound and peripheral ports
• Built-in assembler for time-critical routines
• Last, and by no means least, BBC BASIC has proved robust and relatively error-free for such a complex software product.
Roger Wilson of Acorn, the man behind all the versions of the BASIC interpreter, wrote BASIC I in record time to meet a BBC imposed deadline. In 1983, BASIC 2 corrected (almost!) all the bugs and added the OSCLI and OPENUP keywords. BASIC 4 (Master 128) and BASIC 40 (Master Compact) exploit extra instructions on the 65C12 micro-processor to improve floating-point operations, but the language is essentially identical.
BBC BASIC is an amazing achievement for 16 Kbytes of machine code, but it is capable of improvement. Many of you will be aware that Archimedes BASIC has improved program structures and debugging capabilities - but you would expect no less with half a megabyte of ROM available for RISC-OS, BASIC and applications.
When I first considered modifying BASIC, the Archimedes was far over the horizon. However, I spend much of my professional life doing scientific programming on Hewlett-Packard workstations which make the Archimedes look rather sluggish. The 'Rocky Mountain' BASIC on these machines includes almost every conceivable facility - the technical manuals take up over two feet of shelf space - and it provided an initial list of desirable features.
To get started, I needed a lot of detailed information about BASIC and I was lucky enough to discover a copy of 'The BBC Micro Compendium' in a local library. Acorn banned this book when they found it included a commented dis-assembly of the BASIC 2 ROM. However, a few copies leaked into circulation.
The primary objectives of XBASIC were as follows:
(1) Add extra programming structures to BASIC to simplify program design and maintainance:
• Multi-line IF...ELSE...IF blocks
• WHILE.... WEND loops
• ON <variable> PROC statements (as BASIC 4/40)
• GOTO, GOSUB and RESTORE to program labels
(2) Add useful functions such as MAX and MIN
(3) Speed up the floating point operations of BASIC
(4) Include extra debugging tools and utilities:
• Improve the format of program listings with correct structure-indentation and optional splitting of multi-statement lines into individual lines
• Search a program for a selected string
• No screen corruption and a single-step option on TRACE
• List/verify a program direct from disc
• List program variables
(5) XBASIC should run any BBC BASIC program correctly and be completely compatible with all BASIC versions and all BBC machines (B/B+/Master 128/Master Compact/Electron)
The XBASIC ROM that now sits in my Master 128 - two years and hundreds of programming hours later - contains all these features and many more. So how was it done?
The obvious approach would be to rewrite BASIC more compactly to leave space for extra features, but Roger Wilson's machine code is written with a tautness and elegance which leaves very little slack.
I briefly toyed with the idea of writing a new interpreter which omitted some less-used facilities. This would have been a massive task, but it was the method chosen by the authors of BBC COMAL. Although COMAL is certainly well-structured, I believe the sacrifices involved - especially the lack of compatibility with BASIC programs and the loss of the built-in assembler - are too great to tolerate.
The next option I analysed required the use of a 6502 second processor. If you run BASIC on these machines, there is a big gap in the memory map, from &C000 upwards, where the extra code could be in-serted. Colin Dean exploited this technique when he wrote Advanced BASIC for Tubelink to emulate BASIC 5 on the Archimedes. If you wish to be compatible with BASIC 5 (down to the level of keyword tokenisation) and own a second processor look no further - the product works very well. However, the vast majority of BBC users don't have a second processor and l wanted to add keywords that don't exist on the Archimedes.
The fourth technique I considered is discussed in the 'BASIC ROM User Guide' by Mark Plumbley. When the BASIC interpreter cannot make any sense of a statement, it signals an error by executing a BRK instruction. The operating system then transfers control to an address contained in the BRK vector. This normally points to an error handler set up by BASIC, but control can be redirected to code in another ROM.
This code must first decide if the statement corresponds to a extra facility, then execute appropriate instructions and finally jump back into BASIC. The overall effect is that BASIC is normally in control, but occasionally another ROM seizes the reins.
Although relatively simple in principle, the practical problems are immense. Errors can have all sorts of causes which can only be analysed by examining the state of the 6502 stack and the BASIC program pointers. I decided that the method was unreliable - machine 'crashes' would result from incorrect analysis - and in any case it would slow down program execution drastically. The technique I finally implemented reverses the above procedure. The XBASIC ROM is normally in control and can recognise all BASIC commands plus the XBASIC extensions. When it wishes to perform selected routines, it transfers control to BASIC. After the routine is executed, control returns automatically to XBASIC. As the transfer of control takes place in a well-defined fashion, this procedure avoids the hazards and high overheads of the BRK intercept method.
I had better describe this technique in more detail. I started off by searching the BASIC 2 ROM for large blocks of self-contained code which take several milliseconds to execute. These blocks must terminate with an RTS instruction or an error rather than JMPing back to the infinite loop which is the core of the interpreter. I identified nine such blocks - a typical example is the routine which deletes lines from a program - totalling about 4 Kbytes. Deleting these blocks from the ROM provides the space for the machine code which handles the new facilities. This procedure has the huge advantage that the bulk of XBASIC consists of efficient, debugged BASIC 2 code but leaves three major problems to be overcome:
(1) How to recognise the new XBASIC commands.
(2) How to access the BASIC routines deleted from XBASIC.
(3) How to fit the extra XBASIC features in the available space. BASIC is normally limited to a maximum of 128 keywords which are replaced by single-byte tokens on program entry. This reduces the storage space needed for a program and allows efficient analysis and execution of BASIC statements.
To extend the command set, I borrowed an Archimedes technique and used double-token combinations where the first token is either &C6 (for functions) or &C7 (direct commands) or &C8 (program statements). These particular tokens are illegal inside a BASIC program, so there is no possibility of confusion. I had to extend and modify several routines to make double tokenisation work:
(1) The tokeniser routine itself
(2) The keyword table which lists each command together with its token and a flag byte.
(3) The action-address table which tells the interpreter the address of the code which executes each command.
(4) The routine which converts a token back into a keyword. Jumping between ROMs is a delicate affair. Acorn suggest using extended vectors - see the Advanced User Guide for details. This is quite slow and messy, so l adopted a different technique. Every time XBASIC runs a pro-gram, it first finds the rom-slot and version of BASIC and writes a small machine-code patch into its private language workspace in RAM. Whenever it needs to access a BASIC routine, it reads the entry-point address - which varies in each version of BASIC - from a data-table, pokes that address into the patch and executes a JSR to the start of the patch. The patch switches control to BASIC by altering the ROM-select switch at &FE30 and the RAM copy at &F4. It then executes a JSR to the poked address.
When the routine finishes with an RTS, control returns to the patch. The patch switches ROM-selection back to XBASIC and finally executes an RTS to return to the original calling location. This technique is flexible and the overheads are only about 45 microseconds, so performance is only slightly degraded for routines taking several milliseconds. The only drawback is that it is not second-processor compatible. If an error occurs inside the BASIC routine, the operating system automatically transfers control to the XBASIC error-handler.
Code space was reduced by successive refinement of routines and by making several routines multi-purpose. It would have been easier if I had exploited the 65C12 instruction set, but that would have ruled out compatibility with Model B and B+. The majority of the routines are relatively straightforward and most of my time was spent ensuring that they interfaced correctly with the original code and previous modifications. Each new feature was checked in isolation and I used a suite of BASIC programs, hopefully testing most of the ROM, to search for more subtle bugs. The most difficult part of the entire exercise was the attempt to improve floating-point speed. The run-time of many computation intensive programs is dominated by multiply and divide opera-tions. A first re-write of these routines produced an 8% speed-up. I next modified the SQR routine. BASIC2 takes a crude initial estimate and applies five successive iterations of a refinement procedure called the Newton algorithm. I found a very quick way to get an improved initial estimate which only needs three iterations to achieve the same accuracy. When BASIC 40 appeared on the Master Compact, it used faster and more accurate algorithms for all floating point operations. Most of these were too lengthy to be usable in XBASIC, but I did adapt the multiply and divide routines for a 20% speed-up over BASIC 2. This change and the SQR improvement have a knock-on effect on the computation of transcendental functions (eg SIN and EXP) which are speeded up by between 25% and 35%.
The final floating point modification was to the TAN function. (Non-mathematicians should skip the next three sentences. BASIC 2 computes TAN as SIN/COS which is formally correct but rather slow. BASIC IV uses a continued-fraction method for generating transcendentals which is efficient and has much better global accuracy than power series methods. I took a minimax rational approximation for TAN and converted it into continued-fraction form. TAN now runs three times faster than its BASIC 2 equivalent and is slightly more accurate for some ranges of its argument.
If I had realised how much time this project would eventually absorb I might never have started. Now that it is completed, I can start thinking seriously about an Archimedes ... but that is another story. Model B/B+ and Electron owners who are not thinking about an Archimedes might like to turn to the advert in this issue to find out how to obtain a copy of XBASIC. The following listing does not require XBASIC to be installed in your machine for it to work. It comprises two benchmarks which can be run both before & after installation of the XBASIC rom for comparsison. It also demonstrates the advanced LISTO option.
BEGIN LISTING:
232 REM Program Benchmk v1 (BASIC)
1010 REM Savage Floating-point Benchmark
1020 REM Ackermann Recursion Benchmark
1030 REM (C) D.E. Bower June 1989
1040
1050 MODE 7
1060 FOR row=1 TO 2
1070 PRINT TAB(5, row)CHR$130;CHR$141;"Benchmark Programs"
1080 NEXT
1090 PRINT TAB(10,24)CHR$136;CHR$129;"Calculating";
1100
1110 PRINT TAB(0,6); "Savage Floating-point Benchmark"
1120 @%=&A0A:A=1:TIME=0
1140 FOR J%=1 TO 2499
1150 A=TAN(ATN(EXP(LN(SQR(A*A)))))+1
1160 NEXT
1170 PRINT " Result = ";A
1180 PRINT " Time = ";TIME/100; " secs"
1190
1200 PRINT TAB(0,14);"Ackermann Recursion Benchmark"
1210 M%=3
1220 FOR N%=1 TO 4
1230 TIME=0
1240 Z%=FNAck(M%,N%)
1250 PRINT " Ack (";STR$(M%);",";STR$(N%);") = ";Z%;
1260 PRINT TAB(16);"Time = ";TIME/100;" secs"
1270 NEXT
1280 PRINT TAB(10,24);STRING$(15," ");TAB(0,24);
1290 END
1300
1310 DEF FNAck(M%, N%)
1320 IF M%=0 =N%+1
1330 IF N%=0 =FNAck(M%-1,1)
1340 =FNAck(M%-1,FNAck(M%, N%-1))
END LISTING AT LINE 1340
Statistics: Posted by TobyLobster — Thu Jan 02, 2025 8:53 pm