6502_EhBASIC_V2.22/bugsnquirks.txt

433 lines
19 KiB
Plaintext

EhBASIC 2.22 has a few bugs and quirks that you should know about.
1. The interrupt system is severely broken.
When an IRQ occurs and IRQ_vec is called in min_mon.asm the interrupt is only
registered with IRQbase but the interrupt source is not serviced nor is the
interrupt disable bit set at RTI. As a consequence the interrupt is immediately
called again and again and IRQbase is trashed until the interrupt is serviced
by the ON IRQ handler in basic.
The problem is discussed in http://forum.6502.org/viewtopic.php?f=5&t=2411
How I solved the problem for my emulator:
https://github.com/Klaus2m5/AVR_emulated_6502_4SBC/tree/master/EhBASIC
However, you should keep in mind that the interrupt service capacity in EhBASIC
is very limited. In a 2MHz system more than 100 interrupts per second or
interrupts requiring less than 10ms service time should be handled in machine
language. The numbers are worse if the interrupt handler gets more complex.
Of course, a hybrid solution is always possible. You could service the interrupt
in machine language and flag an interrupt to basic only after a buffer is
filled/emptied or a count is reached.
2. String handling when the string is in the input buffer.
fixed in 2.22p3
When a string is entered via the input buffer (Ibuffs - Ibuffe) in Ehbasic it
is transient because it gets overwritten by the next input. In this case it
needs to be copied to the string area. In all other cases the string remains in
place and only a pointer needs to be set to the string location.
There is a bug in EhBASIC to determine where the string is.
!! The original version of this patch broke the number to string conversion as
in STR$(). Github user mgcaret provided a fix included in version 2 !!
LAB_20DC
STX Sendh ; save string end high byte
LDA ssptr_h ; get string start high byte
CMP #>Ram_base ; compare with start of program memory
BCS LAB_RTST ; branch if not in utility area
This does not work, if RAM_base is below the input buffer or EhBASIC itself is
below RAM_base. The fix requires the input buffer not to cross a page boundary:
LAB_20DC
STX Sendh ; save string end high byte
LDA ssptr_h ; get string start high byte
; *** begin RAM above code / Ibuff above EhBASIC patch V2 ***
; *** replace
; CMP #>Ram_base ; compare with start of program memory
; BCS LAB_RTST ; branch if not in utility area
; *** with
BEQ LAB_MVST ; fix STR$() using page zero via LAB_296E
CMP #>Ibuffs ; compare with location of input buffer page
BNE LAB_RTST ; branch if not in utility area
LAB_MVST
; *** end RAM above code / Ibuff above EhBASIC patch V2 ***
3. Output of some functions limited to integers is negative.
This is not a bug but documented behavior.
Affected functions FRE(), SADD(), VARPTR(), DEEK()
If you want to use FRE() with large RAM you must write:
? FRE(0)-(FRE(0)<0)*$10000
If you really want to deviate from the beaten path, you can add a routine to
convert unsigned integers and jump to it from the functions that you want to
respond with an unsigned number. It replaces LAB_AYFC for these functions.
; save and convert unsigned integer AY to FAC1
LAB_UAYFC
LSR Dtypef ; clear data type flag, $FF=string, $00=numeric
STA FAC1_1 ; save FAC1 mantissa1
STY FAC1_2 ; save FAC1 mantissa2
LDX #$90 ; set exponent=2^16 (integer)
SEC ; always positive
JMP LAB_STFA ; set exp=X, clear FAC1_3, normalise and return
You cannot apply this routine to operators which also use LAB_AYFC (=, OR, AND
EOR <<, >>) as they require signed integers. The value -1 is asigned to a
comparison evaluating as TRUE. Therefore logical operators must be able to
evaluate -1 correctly.
4. Use of decimal mode and invalid BCD
fixed in 2.22p
There is only one place in EhBASIC where decimal mode is used. HEX$() uses
invalid BCD to convert a number to a hexadecimal string. Some processors do not
support decimal mode and emulators most of the time will not support invalid
BCD (nibbles in the range of $A - $F).
LAB_AL2X
CMP #$0A ; set carry for +1 if >9
ADC #'0' ; add ASCII "0"
STA (str_pl),Y ; save to temp string
DEY ; decrement counter
RTS
A patch is available to disable use of decimal mode in EhBASIC.
LAB_AL2X
CMP #$0A ; set carry for +1 if >9
; *** begin disable decimal mode patch ***
; *** insert
BCC LAB_AL20 ; skip adjust if <= 9
ADC #$06 ; adjust for A to F
LAB_AL20
; *** end disable decimal mode patch ***
ADC #'0' ; add ASCII "0"
STA (str_pl),Y ; save to temp string
DEY ; decrement counter
RTS
At the same time you must disable SED & CLD at the HEX$() function.
LAB_HEXS
CPX #$07 ; max + 1
BCS BinFErr ; exit if too big ( > or = )
STX TempB ; save # of characters
LDA #$06 ; need 6 bytes for string
JSR LAB_MSSP ; make string space A bytes long
LDY #$05 ; set string index
; *** disable decimal mode patch - comment next line ***
; SED ; need decimal mode for nibble convert
LDA nums_3 ; get lowest byte
JSR LAB_A2HX ; convert A to ASCII hex byte and output
LDA nums_2 ; get middle byte
JSR LAB_A2HX ; convert A to ASCII hex byte and output
LDA nums_1 ; get highest byte
JSR LAB_A2HX ; convert A to ASCII hex byte and output
; *** disable decimal mode patch - comment next line ***
; CLD ; back to binary
5. Ibuffs located at $xx00
fixed in 2.22p
If the input buffer is located at the beginning of a page then any direct
statement (RUN, LIST, ...) is read from Ibuffs-$100 resulting in unexpected
behavior of EhBASIC.
A patch is available from Daryl Rictor. I took the freedom to add conditional
assembly.
LAB_142A
INY ; increment pointer
INY ; increment pointer (makes it next line pointer high byte)
STA Ibuffs,Y ; save [EOL] (marks [EOT] in immediate mode)
INY ; adjust for line copy
INY ; adjust for line copy
INY ; adjust for line copy
; *** begin patch for when Ibuffs is $xx00 - Daryl Rictor ***
; *** insert
.IF Ibuffs&$FF==0
LDA Bpntrl ; test for $00
BNE LAB_142P ; not $00
DEC Bpntrh ; allow for increment when $xx00
LAB_142P
.ENDIF
; *** end patch for when Ibuffs is $xx00 - Daryl Rictor ***
; end of patch
DEC Bpntrl ; allow for increment
RTS
The conditional in the patch above requires Ibuffs to be known at pass 1 during
assembly. The standard definition of Ibuffs does not satisfy this requirement.
We need to replace it like below:
; Ibuffs can now be anywhere in RAM, ensure that the max length is < $80,
; the input buffer must not cross a page boundary and must not overlap with
; program RAM pages!
;Ibuffs = IRQ_vec+$14
Ibuffs = VEC_SV+$16
; start of input buffer after IRQ/NMI code
Ibuffe = Ibuffs+$47; end of input buffer
6. First statement after direct mode does not set the continue pointer
fixed in 2.22p2
After a RUN or GOTO in direct mode CONT does not work for the first statement in
run mode. It throws a "Can't continue error". If that first statement is INPUT
then "Redo from start" causes a subsequent syntax error.
It is actually a hen and egg problem as the continue pointer (Cpntrh/l) is saved
during the inner loop if not in direct mode, but direct mode (Clineh = $FF) is
only cleared later in the same loop.
The fix is in the patched folder (version 2.22p2). The continue counter is now
always saved and the decision to continue is postponed until the CONT statement
is executed and based on the fact that the continue pointer must not point to
the input buffer in run mode.
LAB_1491
LDX #des_sk ; set descriptor stack pointer
STX next_s ; save descriptor stack pointer
PLA ; pull return address low byte
TAX ; copy return address low byte
PLA ; pull return address high byte
STX LAB_SKFE ; save to cleared stack
STA LAB_SKFF ; save to cleared stack
LDX #$FD ; new stack pointer
TXS ; reset stack
LDA #$00 ; clear byte
;*** fix p2: no longer necessary as the continue pointer is saved anyway
; STA Cpntrh ; clear continue pointer high byte
STA Sufnxf ; clear subscript/FNX flag
.........................................................................
LAB_15C2
JSR LAB_1629 ; do CRTL-C check vector
LDA Bpntrl ; get BASIC execute pointer low byte
LDY Bpntrh ; get BASIC execute pointer high byte
LDX Clineh ; continue line is $FFxx for immediate mode
; ($00xx for RUN from immediate mode)
INX ; increment it (now $00 if immediate mode)
;*** fix p2: skip no longer necessary as the continue pointer is saved anyway
; BEQ LAB_15D1 ; branch if null (immediate mode)
STA Cpntrl ; save continue pointer low byte
STY Cpntrh ; save continue pointer high byte
.........................................................................
LAB_163B
BNE LAB_167A ; if wasn't CTRL-C or there is a following byte return
LDA Bpntrh ; get the BASIC execute pointer high byte
;*** fix p2: skip no longer necessary as the continue pointer is saved anyway
; EOR #>Ibuffs ; compare with buffer address high byte (Cb unchanged)
; BEQ LAB_164F ; branch if the BASIC pointer is in the input buffer
; ; (can't continue in immediate mode)
; ; else ..
; EOR #>Ibuffs ; correct the bits
LDY Bpntrl ; get BASIC execute pointer low byte
.........................................................................
LAB_CONT
BNE LAB_167A ; if following byte exit to do syntax error
LDY Cpntrh ; get continue pointer high byte
CPY #>Ibuffs ; *** fix p2: test direct mode
BNE LAB_166C ; go do continue if we can
.........................................................................
LAB_1934
JSR LAB_CKRN ; check not Direct, back here if ok
JSR LAB_INLN ; print "? " and get BASIC input
LDA #$00 ; set mode = INPUT
CMP Ibuffs ; test first byte in buffer
BNE LAB_1953 ; branch if not null input
; *** change p2: keep carry set to throw break message
; CLC ; was null input so clear carry to exit program
JMP LAB_1647 ; go do BREAK exit
7. String compare of equal strings in direct mode returns FALSE
fixed in 2.22p4
The string descriptor pointer of the 1st string is loaded to FAC1 mantissa
bytes 2 & 3 and is later tranferred to FAC2 via the stack. The subroutine to put
FAC1 on the stack is rounding up if FAC1_r is > $7F. In string operations FAC1_r
is not initialized and rounding up may offset the string pointer high byte.
A patch is available to initialize FAC1_r to < $80 if the variable type is
string.
LAB_1C25
; *** begin patch string pointer high byte trashed when moved to stack
; *** add
LSR FAC1_r ; clear bit 7 (<$80) = do not round up
; *** end patch
RTS
8. FALSE value stored to a variable after string compare is not exactly zero
fixed in 2.22p4
When strings are compared and the result is stored to a variable (x=a$=b$) a
FALSE result is not exactly 0 but only less than 1-E16. So IF var evaluates as
true and falsely executes the statements after THEN.
After a compare FAC1 contains the result. The LET command should store FAC1 into
the variable but actually stores a string pointer.
A patch is available to fix the LET command.
LAB_LET
JSR LAB_GVAR ; get var address
STA Lvarpl ; save var address low byte
STY Lvarph ; save var address high byte
LDA #TK_EQUAL ; get = token
JSR LAB_SCCA ; scan for CHR$(A), else do syntax error then warm start
LDA Dtypef ; get data type flag, $FF=string, $00=numeric
PHA ; push data type flag
JSR LAB_EVEX ; evaluate expression
PLA ; pop data type flag
ROL ; set carry if type = string
; *** begin patch result of a string compare stores string pointer to variable
; but should store FAC1 (true/false value)
; *** replace
; JSR LAB_CKTM ; type match check, set C for string
; BNE LAB_17D5 ; branch if string
; *** with
JSR LAB_CKTM ; type match check, keep C (expected type)
BCS LAB_17D5 ; branch if string
; *** end patch
JMP LAB_PFAC ; pack FAC1 into variable (Lvarpl) and return
9. The stack floor protection does not cater for background interrupts
fixed in 2.22p4
EhBASIC makes heavy use of the stack. FOR, DO and GOSUB put their data
structures on the stack and can be nested only limited by the stack size. The
remaining free bytes on the stack are checked everytime a data structure is to
be stored on the stack. However, this check is not catering for the additional
stack space required by background interrupts of a monitor or operating system.
A patch is available to configure a value to raise the protected stack floor.
LAB_1212
; *** patch - additional stack floor protection for background interrupts
; *** add
.IF Stack_floor
CLC ; prep ADC
ADC #Stack_floor ; stack pointer lower limit before interrupts
.ENDIF
; *** end patch
STA TempB ; save result in temp byte
TSX ; copy stack
CPX TempB ; compare new "limit" with stack
BCC LAB_OMER ; if stack < limit do "Out of memory" error then warm start
RTS
You should also define Stack_floor with other configuation items. I put it just
after RAM_top.
Stack_floor = 16 ; bytes left free on stack for background interrupts
10. Conditional NEXT throws a "NEXT without FOR Error"
fixed in 2.22p4
If a NEXT statement is executed after IF THEN the FOR structure cannot be found.
The NEXT expects the FOR structure at a fixed offset on the stack but the
IF THEN puts an additional address on the stack. The additional address is
required as EhBASIC allows an ELSE statement on the same line and the statement
after the ELSE needs to be skipped.
The same is also true for other commands that need to find a data structure on
the stack. The code already contained a fix for a conditional RETURN but it
could not be used for LOOP (UNTIL/WHILE) or NEXT as it does not allow resuming
execution after falling through.
I have a fix that simply discards the original call to the "interpret statement"
routine (The one that executed the IF) and replaces the final RTS with a JMP to
the interpreter loop. This also eliminates the need to have a separate fix for
RETURN. To my big surprise even nested IFs work correctly.
LAB_174D
; *** patch allow NEXT, LOOP & RETURN to find FOR, DO or GOSUB structure on stack
; *** replace
; CMP #TK_RETURN ; compare the byte with the token for RETURN
; BNE LAB_174G ; if it wasn't RETURN go interpret BASIC code from (Bpntrl)
; ; and return to this code to process any following code
;
; JMP LAB_1602 ; else it was RETURN so interpret BASIC code from (Bpntrl)
; ; but don't return here
;
;LAB_174G
; JSR LAB_15FF ; interpret BASIC code from (Bpntrl)
;
;; the IF was executed and there may be a following ELSE so the code needs to return
;; here to check and ignore the ELSE if present
;
; LDY #$00 ; clear the index
; LDA (Bpntrl),Y ; get the next BASIC byte
; CMP #TK_ELSE ; compare it with the token for ELSE
; BEQ LAB_DATA ; if ELSE ignore the following statement
;
;; there was no ELSE so continue execution of IF <expr> THEN <stat> [: <stat>]. any
;; following ELSE will, correctly, cause a syntax error
;
; RTS ; else return to the interpreter inner loop
;
; *** with
PLA ; discard interpreter loop return address
PLA ; so data structures are at the correct stack offset
JSR LAB_GBYT ; restore token or variable
JSR LAB_15FF ; interpret BASIC code from (Bpntrl)
; the IF was executed and there may be a following ELSE so the code needs to return
; here to check and ignore the ELSE if present
LDY #$00 ; clear the index
LDA (Bpntrl),Y ; get the next BASIC byte
CMP #TK_ELSE ; compare it with the token for ELSE
BNE LAB_no_ELSE ; no - continue on this line
JSR LAB_DATA ; yes - skip the rest of the line
; there was no ELSE so continue execution of IF <expr> THEN <stat> [: <stat>]. any
; following ELSE will, correctly, cause a syntax error
LAB_no_ELSE
JMP LAB_15C2 ; return to the interpreter inner loop
; *** end patch allow NEXT, LOOP & RETURN to find FOR, DO or GOSUB structure on stack
11. Additional bugs brought up by users Ruud and dclxvi of the 6502.org forum
See http://forum.6502.org/viewtopic.php?f=5&t=5500
sanity check for RAM top allows values below RAM base
http://forum.6502.org/viewtopic.php?f=5&t=5606
#1 TO expression with a subtract may evaluate with the sign bit flipped
#3 call to LAB_1B5B may return to an address -$100 (page not incremented)
#4 string concatenate followed by MINUS or NOT() crashes EhBASIC
#5 garbage collection may cause an overlap with temporary strings
#6 floating point multiply rounding bug
#7 VAL() may cause string variables to be trashed
All above have been fixed in 2.22p5. See the thread for additional quirks.