Running BASIC Code from OptROM
This is a place for collecting thoughts on how to embed and run BASIC code in an OptROM. Clearly, this is possible since UR2 implements Idea as a BASIC program.
Short Conversation from M100SIG
Fm: Stan Wong 70346,1267 To: Denny Thomas 76701,40
Paul's on the right track about the Basic interpreter. The Basic code never moves but there is a ROM call that interprets each tokenized statement. Other ROM routines find the next statement to execute. Its been a while but my memory is sort of dim on the subject but each BASIC line in storage is prefixed with the next line pointer and the line number. followed by the statement tokens followed by a null. Three nulls indicate the end of the program (end-of-statement null followed by a line number of zero).
A small program could switch to the OptROM, read a BASIC line (tokenized) into RAM, switch back to system ROM, and then feed it to the interpreter loop. There is no housekeeping since BASIC takes care of it. The pointer to the next statement needs to be fixed up also. Anyway, it's a fairly starightforward proposition. If there is any interest I can dive back into my notes and re-figure out what's what (in any case I got all my info from Robert Covington's ROM maps in the DLs).
Thoughts from Ken Pettit
I have looked at the BASIC interpreter quite a bit. The main program execution loop (for M100) is at address 0804h. The first few function calls perform things like checking for ON COM, ON TIME, CTRL-BREAK, etc. The middle part executes the next instruction, and the last part steps forward to the next instruction / line #. This routine would have to be rewritten to run in RAM and fetch BASIC lines from OptROM. Additionally, the routine below it would have to be rewritten too - it pushes the 0804h return address on the stack that most BASIC instructions use by doing a simple "RET' . This would have to push the address of the re-written RAM based loop.
The problem areas will be in handling the ON TIME, ON COM, ON KEY, and STOP (CTRL-BREAK uses the STOP instruction). From what I remember, these perform an absolute "JMP" into the ROM which then lead back to the 0804h loop and/or search for line numbered BASIC lines in RAM.
Seems like GOTO and DATA/RESTORE/READ statements may be a problem too (BASIC commands that "lookup" line numbers). You might find that several functions need to be re-implemented in the OptROM and ran from there, then switch to the main ROM to run the rest.
Callers of BASIC Inner Interpreter
The following routines enter BASIC 0x0804 with a JMP (as opposed to returning to 0x0804 on the stack):
CLOAD, CLOADM, CLEAR, NEXT, Input Routine, Various places in Menu.
How the BASIC Interpreter Works
This disassembly of the Altair BASIC interpreter, though not the same code, should shed some light since it is so well commented (Philip Avery hipped me to this resource):
In particular, note the "Execution" section 1.9 which is relevant to this problem.
Running BASIC Programs: Excerpt from Hidden Powers (Chris Morgan)
The next major area of memory contains the code that runs BASIC programs. This is the heart of the BASIC interpreter. This section extends from about 804h = 2052d to 871h = 2161d.
This code has to check several conditions as it keeps running your program. The first thing it checks is the communications line, by calling a routine located at 6D6Dh = 28,013d (see Chapter 7). Then it checks for an interrupt from the real-time clock by calling a routine at 4028h = 16424d (see Chapter 5). Next it checks for a break from the keyboard by calling a routine at 13F3h = 5107d (see Chapter 6).
The BASIC interpreter then looks for a colon indicating the next statement of a multiple statement on a BASIC command line. Finally, it checks for the end of the program. It returns to the command loop via a jump to 428h = 1064d if the program has indeed ended.
If the program has not ended, interpretation continues. The current line number is stored in location F67Ah = 63,098d, and then the interpreter dispatches to the routine to handle the keyword for the BASIC command currently under consideration. It points to the address table for BASIC commands. It is interesting to note that the last part of the dispatching routine (locations 858h = 2136d) uses the same code as is used by the RST 2 routine to check for numbers and skip over spaces.
Commented M100 "Inner Interpreter" Code
This commented disassembly is based on merging the Altair walkthrough with the disassembly of the M100 BASIC ROM produced by VirtualT.
In Forth circles, this code is called the "inner interpreter." The "prompt" that accepts BASIC commands is the outer interpreter.
; ====================================================== ; 'ExecNext' Execute BASIC program ; ====================================================== 0804H (CDH) CALL 6D6DH ; Check RS232 queue for pending characters 0807H (C4H) CNZ 4028H 080AH (3AH) LDA F654H 080DH (B7H) ORA A 080EH (C4H) CNZ 402BH 0811H (CDH) CALL 13F3H ; scan for break key 0814H (22H) SHLD FB9BH ; Most recent or currenly running line pointer 0817H (EBH) XCHG 0818H (21H) LXI H,0000H 081BH (39H) DAD SP 081CH (22H) SHLD FB9DH ; SP used by BASIC to reinitialize the stack 081FH (EBH) XCHG ; If we have a ':' then that's a statement seperator (which allows multiple ; statements on the same line) so we jump to Exec to run it. 0820H (7EH) MOV A,M 0821H (FEH) CPI ':' 0823H (CAH) JZ 083AH ; Start executing BASIC program at HL ; Well it wasn't a statement seperator, therefore it must be the null byte ; terminating the line otherwise it's a syntax error. 0826H (B7H) ORA A 0827H (C2H) JNZ 0446H ; Generate Syntax error ; The next two bytes should be the address of the next line. ; We can ignore this, since lines are stored consecutively, ; but must jump back to Main if we've reached the end of the program 082AH (23H) INX H 082BH (7EH) MOV A,M 082CH (23H) INX H 082DH (B6H) ORA M 082EH (CAH) JZ 0428H ; exit to interactive interpreter ; Get the number of the following program line, store it in CURRENT_LINE and fall into Exec to run it. 0831H (23H) INX H 0832H (5EH) MOV E,M 0833H (23H) INX H 0834H (56H) MOV D,M 0835H (EBH) XCHG 0836H (22H) SHLD F67AH ; Current executing line number 0839H (EBH) XCHG ; ====================================================== ; 'Exec' Start executing BASIC program at HL ; ====================================================== 083AH (D7H) RST 2 ; Get next non-white char from M ; Stick address of ExecNext onto stack so we can return to it. 083BH (11H) LXI D,0804H 083EH (D5H) PUSH D ; Return immediately if this is an empty statement 083FH (C8H) RZ ; ====================================================== ; Execute instruction in A, HL points to args ; ====================================================== ; See if we lead with a keyword, ie first byte is >=0x80. If it isn't ; then it might be an implicit LET statement (ie where the LET has not ; been typed) and so we defer to the LET handler to deal with it. 0840H (D6H) SUI 80H 0842H (DAH) JC 09C3H ; LET statement ; If it's not a general keyword then that's a syntax error. 0845H (FEH) CPI 40H 0847H (D2H) JNC 10F4H ; Syntax Error ; So we have a general keyword, so here we get the address of the ; handling function into HL, preserving the program ptr in DE. 084AH (07H) RLC 084BH (4FH) MOV C,A 084CH (06H) MVI B,00H 084EH (EBH) XCHG 084FH (21H) LXI H,0262H ; BASIC statement vector table for END to NEW 0852H (09H) DAD B ; Read the keyword handler function address into BC. 0853H (4EH) MOV C,M 0854H (23H) INX H 0855H (46H) MOV B,M ; Stick keyword handler function address on stack, ; restore program ptr to HL. Fall through to ; RST 10H w/ pre-increment of HL to get next character ; of the statement, and enter the keyword handler ; upon RET. 0856H (C5H) PUSH B 0857H (EBH) XCHG ; ====================================================== ; RST 10H routine with pre-increment of HL ; ====================================================== 0858H (23H) INX H ; ====================================================== ; RST 10H routine ; ====================================================== 0859H (7EH) MOV A,M 085AH (FEH) CPI 3AH 085CH (D0H) RNC 085DH (FEH) CPI 20H 085FH (CAH) JZ 0858H ; RST 10H routine with pre-increment of HL 0862H (FEH) CPI 0BH 0864H (D2H) JNC 086CH 0867H (FEH) CPI 09H 0869H (D2H) JNC 0858H ; RST 10H routine with pre-increment of HL 086CH (FEH) CPI 30H 086EH (3FH) CMC 086FH (3CH) INR A 0870H (3DH) DCR A 0871H (C9H) RET