Bugs & Quirks in the VIC-20 BASIC & KERNAL ROMs

Discuss anything related to the VIC
Post Reply
User avatar
srowe
Vic 20 Scientist
Posts: 1340
Joined: Mon Jun 16, 2014 3:19 pm

Bugs & Quirks in the VIC-20 BASIC & KERNAL ROMs

Post by srowe »

The firmware in the VIC-20 is a remarkable feat of engineering (given the primitive development and debugging tools available at the time it was written). While compared to some other computers of the era it is relatively bug-free there are a number of issues present.

BASIC

VERIFY Result

When the VERIFY command completes it should either
  • display "VERIFY ERROR" if the program does not match
  • display "OK", but only in immediate mode
For the last case the code is

Code: Select all

	LDA	TXTPTR			; get BASIC execute pointer low byte
	CMP	#>BUF			; compare with input buffer high byte
	BEQ	LAB_E194		; skip "OK" prompt
The intention is to compare the high byte of the current BASIC command address with the BASIC input buffer. In program mode this address will be within the tokenized program (i.e. above $1000). The mixup with low vs high byte means this branch is never taken and therefore "OK" is printed, even in program mode.

The code should have been

Code: Select all

	LDA	TXTPTR+1		; get BASIC execute pointer high byte
	CMP	#>BUF			; compare with input buffer high byte
	BNE	LAB_E194		; if program mode skip "OK" prompt
KERNAL

Cassette

The code to handle reading and writing to tape was well tested, being inherited from the PET. One regression did somehow make it into the VIC-20.

Programs can be saved with a logical End of Tape marker by giving a secondary address of 2. This causes a header record with an identifier code of 5 to be written after the program. When loading if the record is reached the operation is terminated. On the PET this reports

Code: Select all

?FILE NOT FOUND ERROR
However, on the VIC-20 the header identifier is taken for the error code to return and

Code: Select all

?DEVICE NOT PRESENT
 ERROR
is displayed instead.

Another mistake is present in the routine blocks waiting for the interrupt-driven part of a read or write operation to complete. The code tests for the read timer interrupt to fire and then attempts to clear it

Code: Select all

	LDA	VIA2IFR			; get VIA 2 IFR
	AND	#%01000000		; mask T1 interrupt
	BEQ	LAB_F92F		; loop if not T1 interrupt

					; else increment jiffy clock
	LDA	VIA1T1CL		; get VIA 1 T1 counter low byte, clear T1 interrupt flag
The wrong VIA is read, the code should have been

Code: Select all

	LDA	VIA2T1CL		; get VIA 2 T1 counter low byte, clear T1 interrupt flag
Keyboard Decode Logic

The routines used to convert key scancodes from the keyboard matrix into PETSCII characters have blocks of NOP instructions in them. There are also conversion tables that are not referenced.

These are remnants of the decode logic that was used in the VIC-1001. The processing of katakana characters required six tables (rather than four) and the character set switching using [SHIFT][C=] was more complex.


Jiffy Clock

The jiffy clock (TI/TI$ in BASIC) starts running from 0 when the computer is powered on. It is intended to wrap after 24 hours, however the code that handles the roll over is

Code: Select all

	SEC				; set carry for subtract
	LDA	TIME+2			; get jiffy clock low byte
	SBC	#$01			; subtract $4F1A01 low byte
	LDA	TIME+1			; get jiffy clock mid byte
	SBC	#$1A			; subtract $4F1A01 mid byte
	LDA	TIME			; get jiffy clock high byte
	SBC	#$4F			; subtract $4F1A01 high byte
	BCC	LAB_F755		; branch if less than $4F1A01 jiffies
This is one jiffy too many. As a result it is possible to get a TI$ value of "240000".

The clock is updated every 1/60th of a second from a timer interrupt. The IRQ routine calls the UDTIM routine, along with other tasks. However UDTIM is also called from the NMI routine. This means that frequent NMIs (e.g. the [RESTORE] key) cause the clock to run fast.


RS-232

The history of the RS-232 interface seems littered with problems. It was originally intended that a UART would be used. At some point the decision was made to implement it in software.

Transmission is a matter of shifting each data byte under a timer interrupt to the CB2 line of VIA1 (along with start and stop
bits). Reception is more complex: a start bit triggers an interrupt on the CB1 line and then a timer interrupt samples the value of the DRA port and shifts it into the result.

RINONE ($A9), the flag used to record when a start bit is received is not initialized when an input channel is set up. As a result a framing error occurs when the first byte is received.

The interrupt pin of VIA1 is connected to the CPU's NMI line. This is edge-triggered: the CPU only calls the NMI vector when the line transitions from high to low. All interrupt sources must be cleared before the line returns to a high level. The NMI routine only processes a single source at a time in the following order:
  • Tx timer
  • Rx timer
  • Rx data start
If data is being transferred in both directions there is a small chance that an additional source triggers while the current one is being processed. This will result in no further interrupts occurring.

The READST routine which is used to return the status flags for the current I/O device was intended to return the value held in RSSTAT ($0297) when the RS-232 interface is in use. The code is

Code: Select all

	LDA	FA			; get device number
	CMP	#$02			; compare device with RS-232 device
	BNE	READIOST		; branch if not RS-232 device

					; get RS-232 device status
	LDA	RSSTAT			; read RS-232 status byte
	LDA	#$00			; clear .A
	STA	RSSTAT			; clear RS-232 status byte
which results in the value $00 always being returned.

The RS-232 interface has two modes of operation: 3-line and x-line. The latter was intended to provide hardware flow control for half-duplex modems. Given the following faults this can never have been actually tested:

When preparing to transmit the DSR and CTS lines should be checked, however the code is

Code: Select all

	BIT	VIA2PB			; test VIA 2 DRB
	BPL	RSMISSNG		; if DSR = 0 set DSR signal not present and exit

	BVC	LAB_F019		; if CTS = 0 set CTS signal not present and exit
The first instruction tests the wrong VIA, it should be

Code: Select all

	BIT	VIA1PB			; test VIA 1 DRB
When preparing to receive the DCD line should be checked, however the code is

Code: Select all

	LDA	VIA1PB			; get VIA 1 DRB
	AND	#%00000100		; mask DTR out
					; DTR is an output and always held high so the
					; following test will never branch
	BEQ	LAB_F138		; loop while DTR low
The correct bit test should be

Code: Select all

	AND	#%00010000		; mask DCD in
	BEQ	LAB_F138		; loop while DCD low
SETTMO & TIMOUT

The Programmers Reference Guide documents this routine as "Set serial bus timeout flag" but the text of the description talks about the DAV signal (which is only present in IEEE-488).

The code is

Code: Select all

FSETTMO
	STA	TIMOUT			; save serial bus timeout flag
	RTS
The location TIMOUT ($0285) is not referenced anywhere in the serial bus code.
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: Bugs & Quirks in the VIC-20 BASIC & KERNAL ROMs

Post by chysn »

Thanks for this treatment! An interesting read.

I was just working with the keyboard decode tables this morning, and I was wondering about the purpose of this whole page of NOPs.

And not necessarily a bug, but they’re not being very elegant about setting the decode table index, either.
VIC-20 Projects: wAx Assembler, TRBo: Turtle RescueBot, Helix Colony, Sub Med, Trolley Problem, Dungeon of Dance, ZEPTOPOLIS, MIDI KERNAL, The Archivist, Ed for Prophet-5

WIP: MIDIcast BASIC extension

he/him/his
User avatar
srowe
Vic 20 Scientist
Posts: 1340
Joined: Mon Jun 16, 2014 3:19 pm

Re: Bugs & Quirks in the VIC-20 BASIC & KERNAL ROMs

Post by srowe »

chysn wrote: Fri Dec 08, 2023 5:54 pm I was just working with the keyboard decode tables this morning, and I was wondering about the purpose of this whole page of NOPs.

And not necessarily a bug, but they’re not being very elegant about setting the decode table index, either.
I've recently disassembled the VIC-1001 ROM in this area, I'll create another "report" on the differences between the VIC-1001 and the VIC-20. The decode logic is significantly more complex, which is why the VIC-20 code looks so odd.
User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: Bugs & Quirks in the VIC-20 BASIC & KERNAL ROMs

Post by Mike »

chysn wrote:I was wondering about the purpose of this whole page of NOPs.
At that time, people were already calling routines inside the KERNAL directly, without using the jump table at the end. The extra decode logic of the VIC-1001 thus needed to be removed without shifting anything else around.

srowe wrote:[...]
Off the top of my head, I'd like to throw in:
  • The defect in the float multiplication routine ("double-0 mantissa", explained here).
  • There's also an obscure bug in the screen editor that can be triggered when deleting characters. Under certain circumstances, not only the remainder of the line is shifted to the left, but an entire memory page:

    Code: Select all

    1 OPEN1,0
    2 PRINT:INPUT#1,A$:PRINTCHR$(20)CHR$(20)"? ";:GOTO 2
    During development of MINIMON, I happened to use the equivalent in machine code to replace the last character in the input by a question mark to indicate errors in input. When [RETURN] is pressed in the right-most column, the screen editor loses track of how many characters to shift in from the right and extends beyond the line for a whole page. In the lower half of the screen, this scrolls in data from beyond the screen buffer. With the screen at $1000 and BASIC at $1201, the test program above is 'shot' immediately - without a single POKE! The error manifests in the handling of the DEL control character in $E77C..$E7A8, but the real culprit is the character scanner in CHRIN (see the handling of $D3 in $E657..$E68F): while scanning a line it moves an 'invisible cursor' over the screen, and adds another column while providing the CR character to signify EOI - which ends up the cursor in a new logical line under the given circumstances (the original input line and the following line had not yet been merged). -- MINIMON now uses a different method to display an input error, which still results in the last character of the input line being replaced by "?", as was intended.
  • Also in the screen editor, when setting colours with POKE646,... the screen editor does not cope with numbers 128 and above. This leads to malfunction of the DEL key (visible in quoted mode). The three instructions at $E7A3..$E7A8 are directly responsible for this - normally BPL $E7F7 is assumed taken. ;)
  • The tape save routines cannot cope with addresses $8000 and above: at some places ($FC06..$FC09, $FC1A..$FC1C, $FC6E..FC73), the MSB of $AD (current address) is (mis-)used to signify the end of the current block. At least somewhat convenient to prevent direct dumps of BLK5 to tape. ;)
  • There's the stack crash in BASIC that can be triggered with PRINT5+"A"+-5
  • There are also missing type checks for non-numeric expressions like in POS() and IF, which fill up the string stack and then lead to ?FORMULA TOO COMPLEX errors.
With the exception of the multiply bug, these bugs are mostly not relevant for regular use (or easy to avoid), but for me the multiply bug was severe enough I decided to replace the BASIC ROM in my VIC-20 with a bug-fixed version.

Greetings,

Michael
groepaz
Vic 20 Scientist
Posts: 1187
Joined: Wed Aug 25, 2010 5:30 pm

Re: Bugs & Quirks in the VIC-20 BASIC & KERNAL ROMs

Post by groepaz »

It should be noted that most (if not all) of this is still broken in the C64 too :)
I'm just a Software Guy who has no Idea how the Hardware works. Don't listen to me.
Post Reply