Running BASIC Code from OptROM: Difference between revisions

From Bitchin100 DocGarden
Jump to navigationJump to search
No edit summary
No edit summary
 
(18 intermediate revisions by 3 users not shown)
Line 5: Line 5:
== Short Conversation from M100SIG ==
== Short Conversation from M100SIG ==


Fm: Stan Wong 70346,1267
Fm: Stan Wong 70346,1267 To: Denny Thomas 76701,40
To: Denny Thomas 76701,40


Paul's on the right track about the Basic interpreter.  The Basic code never
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
moves but there is a ROM call that interprets each tokenized statement.  Other
rom routines find the next statement to execute.  It's been a while but my
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 statement in storage is
memory is sort of dim on the subject but each BASIC line in storage is
prefixed with the line line followed by the statement tokens followed by a
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
null.  Three nulls indicate the end of the program (end-of-statement null
followed by a line number of zero).
followed by a line number of zero).


A small program could switch to the option rom, read a Basic line (tokenized)
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.
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. Oops, I forgot, as part
There is no housekeeping since BASIC takes care of it.
of each line I believe that there is also the address of the next statement.
The pointer to the next statement
That needs to be fixed up also. Anyway, it's a fairly starightforward
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
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
re-figure out what's what (in any case I got all my info from Robert
Covington's ROM maps in the DLs).
Covington's ROM maps in the DLs).
Line 27: Line 26:
== Thoughts from Ken Pettit ==
== Thoughts from Ken Pettit ==


Hi John,
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.


I have looked a 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, etcThe 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.


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):
 
[http://www.interact-sw.co.uk/altair/index2.html Altair BASIC Walk-Through]
 
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.
 
<code><pre>
; ======================================================
; '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
</pre></code>


As for the BASIC debugger, I have the advantage of being able to "see" what the CPU and ROM are doing and can intervene with the "Almighty hand of God" as needed to perform debug operations.  So the debugger isn't bound by the same rules as running from OptROM, but it does need to be aware of the interpreter operation.
[[Category:Model T Developer Reference]] [[Category:Option ROM]]

Latest revision as of 22:24, 18 May 2009

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):

Altair BASIC Walk-Through

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