Mastering Sideways ROM & RAM - Module 17 - Language ROMs -------------------------------------------------------- The first 16 modules of this course have dealt almost exclusively with the design of service ROMs. The rest of the course will be dealing with the design of ROMs that use both language and service entry points. A new language does not need the complexity of a new dialect of BASIC. It simply needs to be a self contained machine code program which is totally independant of any other language. Language ROMs have access to memory from &00 to &8F as zero page workspace, from &0400 to &07FF as main language workspace and from Oshwm to the bottom of screen memory as program space. The program NOBASIC is a good example of an extremely simple language. Unlike BASIC it has no keywords and only processes * commands. It uses an Osword call to read input from the keyboard into a buffer in page &07 and passes that input to the command line interpreter (CLI) for further processing. The return from the CLI goes back for more input which is passed to the CLI, and so on and on until another language is selected to replace it. Although NOBASIC has no keywords it meets all the requirements for a language. It is self contained and completely independant of any other language. To use the language load the object code generated by NOBASIC into SWR and press the Break key. Type *NOBASIC and press Return. The language title and a "*" prompt will be printed. The language will only allow you to use operating system, filing system and service ROM commands. There is no need to type the * prefix for these commands. If, for example, you want to return to BASIC just type BASIC. When starting up a language the MOS does the following: 1. Records the number of the ROM so that it can reselect the language when a BRK instruction is executed and when the Break key is pressed (a "soft" break). 2. Displays the ROM title string to indicate the particular language is in use. 3. Points the error message vector to the copyright message, or the version string if present. 4. If a second processor is active, copies the language across the Tube and executes it there. The service code is not copied across the Tube. 5. Enters the language at its language entry point with the accumulator containing the value &01 to indicate a normal startup. As well as providing a language entry point, a language ROM must provide a service entry point which gives access to a simple one command interpreter to recognise the language name as a command. Upon recognising the language name the ROM must load the X register with its ROM number and call Osbyte &8E to start up the language. Languages other than BASIC can only be started up using Osbyte &8E with the ROM number of the language in the X register. The service entry point must also provide a help service which responds to *HELP by printing the language title string. When a language ROM is entered at its language entry point it must do the following: a. Enable interupts, otherwise the operating system will fail to work. b. Reset the stack pointer. The stack is undefined on entry. c. Set up the error handling routine. Even a simple language such as NOBASIC requires error handling. The operating system automatically switches to the current language ROM on a BRK. When a BRK occurs, the currently active ROM number is stored and can be read using Osbyte &BA. The NOBASIC error handler does not use Osbyte &BA. It prints the error message pointed to by the error vector and resets the language by jumping back to the language entry point. The ROM header of a language ROM has to be configured appropriately in order that the MOS can recognise the ROM image as a language ROM. The difference between service ROM and language ROM headers is that the latter has a jump to the language entry point in the first three bytes of the header and the ROM type byte at &8006 usually stores the number &C2 to indicate that language and service entry points are available. The ROM header for NOBASIC is shown in figure 17.1. ROM headers were described in detail in Module 3 of the course. You may need to look again at Module 3 if you have forgotten about ROM headers and ROM type bytes. 8000 4C 82 80 JMP language \ jump to language entry point 8003 4C 2D 80 JMP service \ jump to service entry point 8006 C2 EQUB &C2 \ ROM type bute (1100 0010) 8007 11 EQUB copyright MOD 256 \ copyright offset 8008 00 BRK \ binary version number (zero) 8009 .title \ ROM title string 8009 4E 4F 42 41 53 49 43 EQUS "NOBASIC" 8010 00 BRK 8011 .copyright \ copyright string 8011 00 BRK \ must start with zero 8012 28 43 29 31 39 38 37 20 47 6F 72 64 6F 6E 20 48 6F 72 73 69 6E 67 74 6F 6E EQUS "(C)1987 Gordon Horsington" 802B 00 BRK \ and end with zero 802C .service \ service entry point . . . 8082 .language \ language entry point . . . Figure 17.1 The NOBASIC ROM header. ----------- ----------------------- The service entry point of NOBASIC gives access to an unrecognised * command interpreter and a simple help service. The registers are pushed on the stack (lines 350-390). The original content of the accumulator is read from the stack (lines 400-410) and compared with the unrecognised command and help service request numbers (lines 420-450). If neither service call 4 nor service call 9 is intercepted the registers are restored (lines 470-510) and control is returned to the MOS (line 520). The familiar one command interpreter (lines 530-660) is used to compare the unrecognised command with the title string of the ROM. If a match is found control passes to the label ".found" (line 670). If it is not the registers are restored (line 660 and lines 470-510) and control passed back to the MOS (line 520). When the command *NOBASIC is intercepted the accumulator is loaded with &8E (line 680) and the X register is loaded with the ROM number stored in &F4 (line 690) and a jump is made to Osbyte (line 700) to enter the ROM at its language entry point. There is no return from Osbyte &8E. The simple help routine (line 710-840) is used to print the ROM title in response to the *HELP command. The argument is initialised (line 720) and, if an argumemt has been typed, the zero flag will be clear on return from Gsinit. The zero flag is tested (line 730) and if clear extended help has been requested. Extended help is not usually provided by language ROMs and so the registers are restored (line 730 and lines 470-510) and control is passed back to the MOS (line 520). If simple help is requested the ROM title is printed (lines 740-830), the registers are restored (line 850 and lines 460-510) and control is passed back to the MOS (line 520). Following the Osbyte &8E call (lines 680-700) the ROM will be entered at its language entry point (line 850) via the jump instruction in the ROM header (line 220). The language must enable interupt requests (line 860), reset the stack (lines 870-880) and re-vector the BREAKV at &202 and &203 (lines 890-920) to point to its own error handling routine starting at the label ".error" (line 1180). Error handling in language ROMs is different to that in service ROMs. In a service ROM you copy the error message, preceded and followed by a BRK instruction, into the error buffer and then jump to the first BRK instruction of the error message, (see Module 8). When the MOS processes the BRK instruction it passes the error to the current active language via the break vector. The error message is pointed to by the error vector at &FD and &FE. The language error handler must use the error vector to print out the error message, then re-initialise the stack and pass control back to some well defined part of the language, such as the input prompt. NOBASIC jumps back to the language entry point after printing the error message. A more complex language would probably not use this shortcut. The error handler in NOBASIC is in lines 1180 to 1290. The MOS processes a BRK instruction by passing control, via the break vector, to the label ".error" (line 1180). The error vector points to the byte following the BRK instruction. This is the error number and it is not printed. The error routine prints every byte of the error message (lines 1200-1260) until it reads the zero byte at the end (line 1240) when it prints a new line to make the printed message look neat (line 1280). It then passes control back to the language initialisation routine (line 1290 and lines 850-920) to reset the stack and issue the input prompt. The coding that implements the language is in lines 930 to 1040. This must be one of the smallest languages possible. The input prompt "*" is printed (line 940-950) and an Osword buffer in page &07 is used to input a string of characters from the keyboard (lines 960-990). The address of the Osword buffer is stored in the first two bytes of the Osword parameter block (lines 1050-1100) and this address is sent to the command line interpreter to process the input (lines 1010-1030). After returning from the CLI (1040) control is passed back to the input prompt routine (line 940) for more input. The only error that can be easily generated within the language can be made by pressing the Escape key when inputing text at the keyboard. This error is trapped (line 1000) when Osword returns with the carry flag set. If an escape is flaged it is acknowledged (lines 1120-1130) and a BRK instruction is executed to signal the error (line 1140). The BRK instruction is followed by an error number (line 1150), an error message (line 1160), and an end of message zero byte (line 1170). The error will be dealt with as described above. 10 REM: NOBASIC 20 MODE7 30 HIMEM=&3C00 40 DIM save 50 50 diff=&8000-HIMEM 60 comvec=&F2 70 ROMnumber=&F4 80 errvec=&FD 90 stack=&103 100 breakv=&202 110 buffer=&700 120 gsinit=&FFC2 130 gsread=&FFC5 140 osasci=&FFE3 150 osnewl=&FFE7 160 osword=&FFF1 170 osbyte=&FFF4 180 oscli=&FFF7 190 FOR pass = 0 TO 2 STEP 2 200 P%=HIMEM 210 [ OPT pass 220 JMP language+diff 230 JMP service+diff 240 OPT FNequb(&C2) 250 OPT FNequb((copyright+diff) MOD 256) 260 BRK 270 .title 280 OPT FNequs("NOBASIC") 290 BRK 300 .copyright 310 BRK 320 OPT FNequs("(C)1987 Gordon Horsington") 330 BRK 340 .service 350 PHA 360 TXA 370 PHA 380 TYA 390 PHA 400 TSX 410 LDA stack,X 420 CMP #4 430 BEQ unrecognised 440 CMP #9 450 BEQ help 460 .finish 470 PLA 480 TAY 490 PLA 500 TAX 510 PLA 520 RTS 530 .unrecognised 540 LDX #&FF 550 .comloop 560 INX 570 LDA title+diff,X 580 BEQ found 590 LDA (comvec),Y 600 INY 610 CMP #ASC(".") 620 BEQ found 630 AND #&DF 640 CMP title+diff,X 650 BEQ comloop 660 BNE finish 670 .found 680 LDA #&8E 690 LDX ROMnumber 700 JMP osbyte \ Enter this ROM 710 .help 720 JSR gsinit 730 BNE finish \ No extended help available 740 JSR osnewl 750 LDX #&FF 760 .helploop 770 INX 780 LDA title+diff,X 790 BEQ endtitle 800 JSR osasci 810 BNE helploop 820 .endtitle 830 JSR osnewl 840 JMP finish+diff 850 .language 860 CLI \ Enable interupt requests 870 LDX #&FF 880 TXS \ Reset stack 890 LDA #(error+diff) MOD 256 900 STA breakv 910 LDA #(error+diff) DIV 256 920 STA breakv+1 930 .continue 940 LDA #ASC("*") 950 JSR osasci 960 LDA #0 970 LDX #(block+diff) MOD 256 980 LDY #(block+diff) DIV 256 990 JSR osword 1000 BCS escape 1010 LDX block+diff 1020 LDY block+diff+1 1030 JSR oscli 1040 JMP continue+diff 1050 .block 1060 OPT FNequb(buffer MOD 256) 1070 OPT FNequb(buffer DIV 256) 1080 OPT FNequb(&FE) 1090 OPT FNequb(&20) 1100 OPT FNequb(&7F) 1110 .escape 1120 LDA #&7E 1130 JSR osbyte \ Acknowledge Escape 1140 BRK 1150 OPT FNequb(&11) 1160 OPT FNequs("Escape") 1170 BRK 1180 .error 1190 JSR osnewl 1200 LDY #0 1210 .errorloop 1220 INY 1230 LDA (errvec),Y 1240 BEQ newline 1250 JSR osasci 1260 BNE errorloop 1270 .newline 1280 JSR osnewl 1290 JMP language+diff 1300 .lastbyte 1310 ] 1320 NEXT 1330 INPUT'"Save filename = "filename$ 1340 IF filename$="" END 1350 $save="SAVE "+filename$+" "+STR$~(HIMEM)+" "+STR$~(las tbyte)+" FFFF8000 FFFF8000" 1360 X%=save 1370 Y%=X% DIV 256 1380 *OPT1,2 1390 CALL oscli 1400 *OPT1,0 1410 END 1420 DEFFNequb(byte) 1430 ?P%=byte 1440 P%=P%+1 1450 =pass 1460 DEFFNequw(word) 1470 ?P%=word 1480 P%?1=word DIV 256 1490 P%=P%+2 1500 =pass 1510 DEFFNequd(double) 1520 !P%=double 1530 P%=P%+4 1540 =pass 1550 DEFFNequs(string$) 1560 $P%=string$ 1570 P%=P%+LEN(string$) 1580 =pass