Discussion:
Question concerning Disk II emulation / timing
(too old to reply)
Jens Gaulke
2023-03-24 22:21:50 UTC
Permalink
Hi there,

I tried to do Disk II emulation and implemented all softswitches and the functions as far as I understood them from Jim Sather. The only disk image start starts for me is "Apple Galaxian". A standard DOS 3.3 System Master reads some tracks and then crashes with an illegal opcode. What am I missing? Do I have some timing problems in my emulation? Did I not correctly implement the softswitch functions?

I attached my code (I hope the formatting will not be corrupted) and would be really glad to get an answer ...

import random
import pygame
from dataclasses import dataclass
import threading
import math
import re

@dataclass
class DRIVE_STATE:
filename: str
half_track: int
prev_half_track: int
write_mode: bool
current_phase: int
disk_has_changes: bool
motor_running: bool
track_start: list
track_nbits: list
track_location: int
is_write_protected: bool
disk_data: bytearray
status: str


firmware = [0xA2,0x20,0xA0,0x00,0xA2,0x03,0x86,0x3C,0x8A,0x0A,0x24,0x3C,0xF0,0x10,0x05,0x3C,
0x49,0xFF,0x29,0x7E,0xB0,0x08,0x4A,0xD0,0xFB,0x98,0x9D,0x56,0x03,0xC8,0xE8,0x10,
0xE5,0x20,0x58,0xFF,0xBA,0xBD,0x00,0x01,0x0A,0x0A,0x0A,0x0A,0x85,0x2B,0xAA,0xBD,
0x8E,0xC0,0xBD,0x8C,0xC0,0xBD,0x8A,0xC0,0xBD,0x89,0xC0,0xA0,0x50,0xBD,0x80,0xC0,
0x98,0x29,0x03,0x0A,0x05,0x2B,0xAA,0xBD,0x81,0xC0,0xA9,0x56,0xa9,0x00,0xea,0x88,
0x10,0xEB,0x85,0x26,0x85,0x3D,0x85,0x41,0xA9,0x08,0x85,0x27,0x18,0x08,0xBD,0x8C,
0xC0,0x10,0xFB,0x49,0xD5,0xD0,0xF7,0xBD,0x8C,0xC0,0x10,0xFB,0xC9,0xAA,0xD0,0xF3,
0xEA,0xBD,0x8C,0xC0,0x10,0xFB,0xC9,0x96,0xF0,0x09,0x28,0x90,0xDF,0x49,0xAD,0xF0,
0x25,0xD0,0xD9,0xA0,0x03,0x85,0x40,0xBD,0x8C,0xC0,0x10,0xFB,0x2A,0x85,0x3C,0xBD,
0x8C,0xC0,0x10,0xFB,0x25,0x3C,0x88,0xD0,0xEC,0x28,0xC5,0x3D,0xD0,0xBE,0xA5,0x40,
0xC5,0x41,0xD0,0xB8,0xB0,0xB7,0xA0,0x56,0x84,0x3C,0xBC,0x8C,0xC0,0x10,0xFB,0x59,
0xD6,0x02,0xA4,0x3C,0x88,0x99,0x00,0x03,0xD0,0xEE,0x84,0x3C,0xBC,0x8C,0xC0,0x10,
0xFB,0x59,0xD6,0x02,0xA4,0x3C,0x91,0x26,0xC8,0xD0,0xEF,0xBC,0x8C,0xC0,0x10,0xFB,
0x59,0xD6,0x02,0xD0,0x87,0xA0,0x00,0xA2,0x56,0xCA,0x30,0xFB,0xB1,0x26,0x5E,0x00,
0x03,0x2A,0x5E,0x00,0x03,0x2A,0x91,0x26,0xC8,0xD0,0xEE,0xE6,0x27,0xE6,0x3D,0xA5,
0x3D,0xCD,0x00,0x08,0xA6,0x2B,0x90,0xDB,0x4C,0x01,0x08,0x00,0x00,0x00,0x00,0x00]


DEFAULT_VOLUME = 254
NUM_DRIVES = 2
DOS_NUM_SECTORS = 16
DOS_NUM_TRACKS = 35
DOS_TRACK_BYTES = 256 * DOS_NUM_SECTORS
RAW_TRACK_BYTES = 0x1A00
STANDARD_2IMG_HEADER_SIZE = 64
STANDARD_PRODOS_BLOCKS = 280

# diskdata = [0,1][0,35][1a00]

pickbit = [128, 64, 32, 16, 8, 4, 2, 1]
clearbit = [0b01111111, 0b10111111, 0b11011111, 0b11101111, 0b11110111, 0b11111011, 0b1111101, 0b11111110]


class FloppyDisk:

def __init__(self, myApple, slot) -> None:
self.drive = 0
self.is_motor_on = False
self.is_write_protected = [0,1]
#self.disk_data = [[0 for i in range(2*DOS_NUM_TRACKS)], [0 for i in range(2*DOS_NUM_TRACKS)]]
self.curr_phys_track = 0
self.curr_nibble = 0

self.drive_curr_phys_track = [0,0]
self.real_track = [0 for i in range(RAW_TRACK_BYTES+1)]

self.latch_data = 0
self.write_mode = False
self.load_mode = False
self.drive_spin = False

self.gcr_nibble_pos = 0
self.gcr_nibbles = []

pygame.mixer.pre_init(frequency=44100, size=-16, channels=1, allowedchanges=0)
pygame.init()
pygame.mixer.init(frequency=44100, size=-16, channels=1, allowedchanges=0)
self.motor_sound = pygame.mixer.Sound(f'./src_audio_driveMotor.mp3')
self.track_off_end = pygame.mixer.Sound(f'./src_audio_driveTrackOffEnd.mp3')
self.track_seek = pygame.mixer.Sound(f'./src_audio_driveTrackSeek.mp3')

self.setup_firmware(myApple, slot)

self.drive_state = [DRIVE_STATE('', 0, 0, False, 0, False, False, [None]*80, [None]*80, 0, False, self.load_wozdisk(), ""),
DRIVE_STATE('', 0, 0, False, 0, False, False, [None]*80, [None]*80, 0, False, self.load_wozdisk(), "")]

self.reset_drives()

self.disk_data = [self.load_wozdisk(), self.load_wozdisk()]
#arraypart = self.disk_data[0][0:12]
#print (' '.join('{:02x}'.format(x) for x in arraypart))

self.init_diskstates(0,'Dung Beetles.woz')
self.init_diskstates(1,'DOS_3.3_System_Master.woz')

self.SLOT = slot << 4
self.DRVPH0_OFF = 0xC080 + self.SLOT
self.DRVPH0_ON = self.DRVPH0_OFF + 1
self.DRVPH1_OFF = 0xC082 + self.SLOT
self.DRVPH1_ON = self.DRVPH1_OFF + 1
self.DRVPH2_OFF = 0xC084 + self.SLOT
self.DRVPH2_ON = self.DRVPH2_OFF + 1
self.DRVPH3_OFF = 0xC086 + self.SLOT
self.DRVPH3_ON = self.DRVPH3_OFF + 1
self.DRVMOTOR_OFF = 0xC088 + self.SLOT
self.DRVMOTOR_ON = self.DRVMOTOR_OFF + 1
self.DRVSEL_0 = 0xC08A + self.SLOT
self.DRVSEL_1 = self.DRVSEL_0 + 1
self.DRVDATA_SHIFT = 0xC08C + self.SLOT
self.DRVDATA_LOAD = self.DRVDATA_SHIFT + 1
self.DRVREAD = 0xC08E + self.SLOT
self.DRVWRITE = 0xC08F + self.SLOT
self.DRVPH0_isSet = False
self.DRVPH1_isSet = False
self.DRVPH2_isSet = False
self.DRVPH3_isSet = False
self.DRVMOTOR_isSet = False
self.DRVDATA_isSet = False
self.DRVREAD_isSet = False


self.current_drive = 0
self.prev_cycle = 0
self.data_register = 0
self.sm = None

def setup_firmware(self, myApple, slot):
for i in range(0x100):
myApple.memory.ram.write_byte(0xC000 + (slot << 8) + i, firmware[i])



def load_wozdisk(self):
filename = 'DOS_3.3_System_Master.woz'
#filename = 'Dung Beetles.woz'
#filename = 'Apple DOS 3.3 August 1980.woz'
#filename = 'Choplifter.dsk'
#filename = 'Apple_Galaxian.woz'
diskdata = []
with open(filename, "rb") as f:
diskdata = bytearray(f.read())
return diskdata


def reset_drives(self):
#self.stop_motor(0)
#self.stop_motor(1)
self.drive_state[0].half_track = 68
self.drive_state[1].half_track = 68
self.drive_state[0].prev_half_track = 68
self.drive_state[1].prev_half_track = 68


def is_dsk(self, filename):
isdsk = filename.endswith(".dsk") or filename.endswith(".do")
ispo = filename.endswith(".po")
return isdsk | ispo

def decode_woz2(self, drivestate, filename):
disk_data = drivestate.disk_data
# Überprüfen, ob die Diskette den richtigen Header hat
woz2 = [0x57, 0x4F, 0x5A, 0x32, 0xFF, 0x0A, 0x0D, 0x0A]
found = True
for i in range (len(woz2)):
if woz2[i] != disk_data[i]:
found = False
if not found:
return
if disk_data[22] == 1:
drivestate.is_write_protected = True
else:
drivestate.is_write_protected = False
# checksum überprüfung fehlt noch
# WOZ 2
for htrack in range(80):
tmap_index = disk_data[88 + htrack*2]
if tmap_index < 255:
tmap_offset = 256 + 8*tmap_index
trk = disk_data[tmap_offset:tmap_offset + 8]
drivestate.track_start[htrack] = 512 * (trk[0] + (trk[1] << 8))
drivestate.track_nbits[htrack] = trk[4] + (trk[5] << 8) + (trk[6] << 16) + (trk[7] << 24)
else:
drivestate.track_start[htrack] = 0
drivestate.track_nbits[htrack] = 51200
return True


def decode_woz1(self, drivestate, filename):
disk_data = drivestate.disk_data
# Überprüfen, ob die Diskette den richtigen Header hat
woz1 = [0x57, 0x4F, 0x5A, 0x31, 0xFF, 0x0A, 0x0D, 0x0A]
found = True
for i in range (len(woz1)):
if woz1[i] != disk_data[i]:
found = False
if not found:
return
if disk_data[22] == 1:
drivestate.is_write_protected = True
else:
drivestate.is_write_protected = False
# checksum überprüfung fehlt noch
# WOZ 1
for htrack in range(80):
tmap_index = disk_data[88 + htrack*2]
if tmap_index < 255:
drivestate.track_start[htrack] = 256 + tmap_index * 6656
trk = disk_data[(drivestate.track_start[htrack]+6646):(drivestate.track_start[htrack]+6656)]
drivestate.track_nbits[htrack] = trk[2] + (trk[3] << 8)
else:
drivestate.track_start[htrack] = 0
drivestate.track_nbits[htrack] = 51200
return True


def decode_dsk(self, drivestate, filename):
pass

def decode_diskdata(self, drivenum, filename):
self.drive_state[drivenum].filename = filename
if self.is_dsk(filename):
print("dsk image")
self.decode_dsk(self.drive_state[drivenum], filename)
if self.decode_woz2(self.drive_state[drivenum], filename):
print("woz2 image")
if self.decode_woz1(self.drive_state[drivenum], filename):
print("woz1 image")


def init_diskstates(self, drivenum, filename):
self.decode_diskdata(drivenum, filename)


def set_switches(self, address):
if address == self.DRVPH0_OFF:
self.DRVPH0_isSet = False
if address == self.DRVPH0_ON:
self.DRVPH0_isSet = True
if address == self.DRVPH1_OFF:
self.DRVPH1_isSet = False
if address == self.DRVPH1_ON:
self.DRVPH1_isSet = True
if address == self.DRVPH2_OFF:
self.DRVPH2_isSet = False
if address == self.DRVPH2_ON:
self.DRVPH2_isSet = True
if address == self.DRVPH3_OFF:
self.DRVPH3_isSet = False
if address == self.DRVPH3_ON:
self.DRVPH3_isSet = True
if address == self.DRVMOTOR_OFF:
self.DRVMOTOR_isSet = False
if address == self.DRVMOTOR_ON:
self.DRVMOTOR_isSet = True
if address == self.DRVDATA_SHIFT:
self.DRVDATA_isSet = False
if address == self.DRVDATA_LOAD:
self.DRVDATA_isSet = True
if address == self.DRVREAD:
self.DRVREAD_isSet = False
if address == self.DRVWRITE:
self.DRVREAD_isSet = True


def softswitches(self, address, value, cycle):
self.set_switches(address)
disk_drive = self.drive_state[self.current_drive]
delta = cycle - self.prev_cycle
result = 0
# Phasenmotoren
a = address - self.DRVPH0_OFF
phases = [self.DRVPH0_OFF, self.DRVPH1_OFF, self.DRVPH2_OFF, self.DRVPH3_OFF]
phases_isset = [self.DRVPH0_isSet, self.DRVPH1_isSet, self.DRVPH2_isSet, self.DRVPH3_isSet]
if a >= 0 and a <= 7:
ascend = phases_isset[(disk_drive.current_phase + 1) % 4]
descend = phases_isset[(disk_drive.current_phase + 3) % 4]
# Abfrage, ob der phasenmotor aus ist. wie? Wo wird der softswitch gesetzt?
if not phases_isset[disk_drive.current_phase]:
if disk_drive.motor_running:
if ascend:
#if ascend == address:
self.move_head(disk_drive,1)
disk_drive.current_phase = (disk_drive.current_phase + 1) % 4
#print("ascending")
elif descend:
#elif descend == address:
self.move_head(disk_drive,-1)
disk_drive.current_phase = (disk_drive.current_phase + 3) % 4
#print("descending")
# 0xC088
elif address == self.DRVMOTOR_OFF:
#print("Stop Motor:",self.current_drive)
self.stop_motor(disk_drive, self.current_drive)
return result
# 0xC089
elif address == self.DRVMOTOR_ON:
#print("Start Motor:",self.current_drive)
self.start_motor(disk_drive, self.current_drive)
return result
# 0xC08A && 0xC08B
elif address == self.DRVSEL_1 or address == self.DRVSEL_0:
self.current_drive = 0 if address == self.DRVSEL_0 else 1
if self.drive_state[1 - self.current_drive].motor_running:
self.drive_state[1 - self.current_drive].motor_running = False
self.drive_state[self.current_drive].motor_running = True
return result
# 0xC08C SHIFT
elif address == self.DRVDATA_SHIFT:
if disk_drive.motor_running and not disk_drive.write_mode:
return self.get_next_byte(disk_drive)
# 0xC08D LOAD/READ
elif address == self.DRVDATA_LOAD:
if disk_drive.motor_running:
if disk_drive.write_mode:
self.do_write_byte(delta, disk_drive)
self.prev_cycle = cycle
if value >= 0:
self.data_register = value
# 0xC08E READ
elif address == self.DRVREAD:
if disk_drive.motor_running and disk_drive.write_mode:
self.do_write_byte(delta,disk_drive)
self.prev_cycle = cycle
disk_drive.write_mode = False
# if self.DRVDATA.isset:
# result = disk_drive.iswriteprotected ? 0xff : 0
result = 255
# 0xC08F WRITE
elif address == self.DRVWRITE:
disk_drive.write_mode = True
self.prev_cycle = cycle
if value >= 0:
self.data_register = value

return result



def debug_data(self, address):
pass

def move_head(self, disk_drive, direction):
if disk_drive.track_start[disk_drive.half_track] > 0:
disk_drive.prev_half_track = disk_drive.half_track
disk_drive.half_track += direction
if disk_drive.half_track < 0 or disk_drive.half_track > 68:
# sound: drive.trackend
if disk_drive.half_track < 0:
disk_drive.half_track = 0
if disk_drive.half_track > 68:
disk_drive.half_track = 68
else:
# sound: drive.trackseek
pass

disk_drive.status = str(disk_drive.half_track // 2)

if disk_drive.track_start[disk_drive.half_track] > 0 and disk_drive.prev_half_track != disk_drive.half_track:
disk_drive.track_location = math.floor(disk_drive.track_location * (disk_drive.track_nbits[disk_drive.half_track] / disk_drive.track_nbits[disk_drive.prev_half_track]))
if disk_drive.track_location > 3:
disk_drive.track_location -= 4

#print("Drive track:", disk_drive.half_track //2)


def do_write_byte(self, delta):
print("do write byte")
return

def do_nothing(self):
for i in range(10):
i=i+1
#print("NOP")
pass

def get_next_bit(self, disk_drive):
disk_drive.track_location = disk_drive.track_location % disk_drive.track_nbits[disk_drive.half_track]
bit = 0
if disk_drive.track_start[disk_drive.half_track] > 0:
file_offset = disk_drive.track_start[disk_drive.half_track] + (disk_drive.track_location >> 3)
byte = disk_drive.disk_data[file_offset]
b = disk_drive.track_location & 7
bit = (byte & pickbit[b]) >> (7-b)
else:
bit = 1
disk_drive.track_location += 1
return bit


def get_next_byte(self, disk_drive):
if len(disk_drive.disk_data) == 0 or disk_drive.half_track % 2 != 0:
print("No Disk")
return 0
result = 0
if self.data_register == 0:
while(self.get_next_bit(disk_drive) == 0):
self.do_nothing()
self.data_register = 0x40
for i in range(5,-1,-1):
# print("data register:", i)
self.data_register |= self.get_next_bit(disk_drive) << i
else:
bit = self.get_next_bit(disk_drive)
self.data_register = (self.data_register << 1) | bit
result = self.data_register
if (self.data_register > 127):
self.data_register = 0
print("get next byte:", hex(result), ' ', disk_drive.track_location,' ', disk_drive.half_track)
return result


def start_motor(self, disk_drive, current_drive):
disk_drive.motor_running = True
if self.sm:
self.sm.cancel()
pygame.mixer.Channel((current_drive+1)*2-1).play(self.motor_sound)


def stop_motor(self, disk_drive, current_drive):
self.sm = threading.Timer(1,self.stopit,[disk_drive, current_drive])
self.sm.start()


def stopit(self, disk_drive, current_drive):
disk_drive.motor_running = False
pygame.mixer.Channel((current_drive+1)*2-1).stop()
Michael AppleWin Debugger Dev
2023-03-25 13:44:01 UTC
Permalink
Post by Jens Gaulke
I tried to do Disk II emulation and implemented all softswitches and the functions as far as I understood them from Jim Sather. The only disk image start starts for me is "Apple Galaxian". A standard DOS 3.3 System Master reads some tracks and then crashes with an illegal opcode. What am I missing?
Many nights of poring over boot traces. /s

Seriously, the "secret sauce" is nothing more then hard work of logging to tell where things are going off the rails.
i.e.
When you boot DOS 3.3, what are the PC & regs when it crashes? What stage of DOS doe the PC reach? Stage 0? Stage 1? Stage 2?
Post by Jens Gaulke
Do I have some timing problems in my emulation? Did I not correctly implement the softswitch functions?
The easiest way to debug this would probably be to use a process of elimination as you verify the softswitches one-one by one.

0. Write a small test program to select drive 0, wait, select drive 1, wait to rule our DRVSEL_1 and DRVSEL_2. Yes, this is trivial. Yes, you need to eliminate this.

1. I would then rule out the MOTORON, MOTOROFF. Yes this is again trivial, and yes you need to eliminate this.

2 I would test the four PHASE0..PHASE3 softswitches by writing a small assembly program to move the track outwards 40 tracks and then inwards 40 tracks logging the track number. That leaves four softswitches to test SHIFT, LOAD, READ, WRITE. You don't need to worry about WRITE for now, so this leaves SHIFT, LOAD, and READ.

3. Prepare a "raw disk" so that each of 35 tracks contains nothing but FF filler and only ONE disk nibble of an unique track marker. Modify the utility from step (1) to read the raw 4&4 disk nibble, and step the 35 tracks. This SO question may be helpful as Nick was kind enough to provide some nibble counting code:
https://retrocomputing.stackexchange.com/questions/503/absolute-maximum-number-of-nibbles-on-an-apple-ii-floppy-disk-track/664#664

4. Get .do/.dsk working first before .woz. I don't see your 6&2 decode logic? Have you verified this is correct?

5. You may want to use a "simpler" program to trace. I'm extremely biased so something like Fantavision since I have already written up the boot-tracing :-) but any program/game where you already have boot-tracing notes (say from 4AM) would work.
https://github.com/Michaelangel007/apple2_fantavision_reloaded#ye-olde-boot-tracing

6. I already mentioned the DOS 3.3 System Master above.

7. Use another emulator to debug your emulator. i.e In AppleWin see the disk logging defines, LOG_DISK_PHASES, LOG_DISK_NIBBLES_SPIN, LOG_DISK_NIBBLES_READ, etc. in source/Disk.cpp.

There are also some interesting comments in DataLatchReadWriteWOZ().
Post by Jens Gaulke
I attached my code (I hope the formatting will not be corrupted)
Unfortunately, Python uses the retarded decision of using whitespace for code blocks which sadly means indentation will usually be completely lost due to HTML defaulting to sequences of white space being collapsed. Do you have a git repository where it is easier to read that python code?

Pro-Tip: (Almost) no one wants to read a dump of code, but we can give you pointers if you share more details of what you have tried and where exactly things have stopped working.

Good luck! Keep us posted as you trace through the issue.

Michael
Jens Gaulke
2023-03-29 15:09:48 UTC
Permalink
Post by Michael AppleWin Debugger Dev
Post by Jens Gaulke
I tried to do Disk II emulation and implemented all softswitches and the functions as far as I understood them from Jim Sather. The only disk image start starts for me is "Apple Galaxian". A standard DOS 3.3 System Master reads some tracks and then crashes with an illegal opcode. What am I missing?
Many nights of poring over boot traces. /s
Seriously, the "secret sauce" is nothing more then hard work of logging to tell where things are going off the rails.
i.e.
When you boot DOS 3.3, what are the PC & regs when it crashes? What stage of DOS doe the PC reach? Stage 0? Stage 1? Stage 2?
Post by Jens Gaulke
Do I have some timing problems in my emulation? Did I not correctly implement the softswitch functions?
The easiest way to debug this would probably be to use a process of elimination as you verify the softswitches one-one by one.
0. Write a small test program to select drive 0, wait, select drive 1, wait to rule our DRVSEL_1 and DRVSEL_2. Yes, this is trivial. Yes, you need to eliminate this.
1. I would then rule out the MOTORON, MOTOROFF. Yes this is again trivial, and yes you need to eliminate this.
2 I would test the four PHASE0..PHASE3 softswitches by writing a small assembly program to move the track outwards 40 tracks and then inwards 40 tracks logging the track number. That leaves four softswitches to test SHIFT, LOAD, READ, WRITE. You don't need to worry about WRITE for now, so this leaves SHIFT, LOAD, and READ.
https://retrocomputing.stackexchange.com/questions/503/absolute-maximum-number-of-nibbles-on-an-apple-ii-floppy-disk-track/664#664
4. Get .do/.dsk working first before .woz. I don't see your 6&2 decode logic? Have you verified this is correct?
5. You may want to use a "simpler" program to trace. I'm extremely biased so something like Fantavision since I have already written up the boot-tracing :-) but any program/game where you already have boot-tracing notes (say from 4AM) would work.
https://github.com/Michaelangel007/apple2_fantavision_reloaded#ye-olde-boot-tracing
6. I already mentioned the DOS 3.3 System Master above.
7. Use another emulator to debug your emulator. i.e In AppleWin see the disk logging defines, LOG_DISK_PHASES, LOG_DISK_NIBBLES_SPIN, LOG_DISK_NIBBLES_READ, etc. in source/Disk.cpp.
There are also some interesting comments in DataLatchReadWriteWOZ().
Post by Jens Gaulke
I attached my code (I hope the formatting will not be corrupted)
Unfortunately, Python uses the retarded decision of using whitespace for code blocks which sadly means indentation will usually be completely lost due to HTML defaulting to sequences of white space being collapsed. Do you have a git repository where it is easier to read that python code?
Pro-Tip: (Almost) no one wants to read a dump of code, but we can give you pointers if you share more details of what you have tried and where exactly things have stopped working.
Good luck! Keep us posted as you trace through the issue.
Michael
Jens Gaulke
2023-03-29 15:15:16 UTC
Permalink
Post by Michael AppleWin Debugger Dev
Post by Jens Gaulke
I tried to do Disk II emulation and implemented all softswitches and the functions as far as I understood them from Jim Sather. The only disk image start starts for me is "Apple Galaxian". A standard DOS 3.3 System Master reads some tracks and then crashes with an illegal opcode. What am I missing?
Many nights of poring over boot traces. /s
Seriously, the "secret sauce" is nothing more then hard work of logging to tell where things are going off the rails.
i.e.
When you boot DOS 3.3, what are the PC & regs when it crashes? What stage of DOS doe the PC reach? Stage 0? Stage 1? Stage 2?
Post by Jens Gaulke
Do I have some timing problems in my emulation? Did I not correctly implement the softswitch functions?
The easiest way to debug this would probably be to use a process of elimination as you verify the softswitches one-one by one.
0. Write a small test program to select drive 0, wait, select drive 1, wait to rule our DRVSEL_1 and DRVSEL_2. Yes, this is trivial. Yes, you need to eliminate this.
1. I would then rule out the MOTORON, MOTOROFF. Yes this is again trivial, and yes you need to eliminate this.
2 I would test the four PHASE0..PHASE3 softswitches by writing a small assembly program to move the track outwards 40 tracks and then inwards 40 tracks logging the track number. That leaves four softswitches to test SHIFT, LOAD, READ, WRITE. You don't need to worry about WRITE for now, so this leaves SHIFT, LOAD, and READ.
https://retrocomputing.stackexchange.com/questions/503/absolute-maximum-number-of-nibbles-on-an-apple-ii-floppy-disk-track/664#664
4. Get .do/.dsk working first before .woz. I don't see your 6&2 decode logic? Have you verified this is correct?
5. You may want to use a "simpler" program to trace. I'm extremely biased so something like Fantavision since I have already written up the boot-tracing :-) but any program/game where you already have boot-tracing notes (say from 4AM) would work.
https://github.com/Michaelangel007/apple2_fantavision_reloaded#ye-olde-boot-tracing
6. I already mentioned the DOS 3.3 System Master above.
7. Use another emulator to debug your emulator. i.e In AppleWin see the disk logging defines, LOG_DISK_PHASES, LOG_DISK_NIBBLES_SPIN, LOG_DISK_NIBBLES_READ, etc. in source/Disk.cpp.
There are also some interesting comments in DataLatchReadWriteWOZ().
Post by Jens Gaulke
I attached my code (I hope the formatting will not be corrupted)
Unfortunately, Python uses the retarded decision of using whitespace for code blocks which sadly means indentation will usually be completely lost due to HTML defaulting to sequences of white space being collapsed. Do you have a git repository where it is easier to read that python code?
Pro-Tip: (Almost) no one wants to read a dump of code, but we can give you pointers if you share more details of what you have tried and where exactly things have stopped working.
Good luck! Keep us posted as you trace through the issue.
Michael
Hi all,

I found a very stupid error. To do some testing, I had implemented a Routine for changing values in the ROM.
The call to this Routine was still active, and so while booting up DOS, DOS tried to access ROM addresses and changed contents of the ROM. This caused the illegal Instruction I talked about. I disabled the writing to the ROM and the dsk booted up. So I learned, that DOS tries to access the 16k RAM Card.

On top of that I remembered that french_postcards needed a RAM expansion to boot up properly and I implemented the 16k language Card. The 16k are working well and some dsk are booting up properly.

At the moment , I try to unify the woz and dsk routines to make handling easier.

There are just two things that bother me. Booting up karateka does not switch to graphics mode correctly though I Implemented all the softswitches and they work correctly When testing via AppleSoft. Second topic: booting a Disk is slooooow. I have no idea how to Speed that up at the Moment.

That's how it is going at the moment. Thank you all for your support!

Cheers, Jens
D Finnigan
2023-03-29 20:11:29 UTC
Permalink
Post by Jens Gaulke
To do some testing, I had implemented a
Routine for changing values in the ROM.
The call to this Routine was still active, and so while booting up DOS,
DOS
tried to access ROM addresses and changed contents of the ROM. This caused
the illegal Instruction I talked about.
Back in the old days, Apple Computer used to always guarantee that the
contents of ROM would never change during execution of your program.
Probably this is the reason why.

;-)
--
]DF$
The New Apple II User's Guide:
https://macgui.com/newa2guide/
Michael AppleWin Debugger Dev
2023-03-30 18:25:57 UTC
Permalink
Post by Jens Gaulke
I found a very stupid error. To do some testing, I had implemented a Routine for changing values in the ROM.
The call to this Routine was still active, and so while booting up DOS, DOS tried to access ROM addresses and changed contents of the ROM. This caused the illegal Instruction I talked about. I disabled the writing to the ROM and the dsk booted up. So I learned, that DOS tries to access the 16k RAM Card.
Yes, sadly DOS 3.3 rudely futzes with the language card at 3FCB:

3FCB:AD 81 C0 022 LDA $C081 ; magic number If only ROMIN = $C081 for Language Card (LC)
3FCE:AD 81 C0 023 LDA $C081 ; magic number 1st read = read ROM, 2nd read = write enable LC Bank 2
3FD1:A9 00 024 LDA #0
3FD3:8D 00 E0 025 STA $E000
Post by Jens Gaulke
On top of that I remembered that french_postcards needed a RAM expansion to boot up properly and I implemented the 16k language Card. The 16k are working well and some dsk are booting up properly.
Awesome to hear!
Post by Jens Gaulke
There are just two things that bother me. Booting up karateka does not switch to graphics mode correctly though I Implemented all the softswitches and they work correctly
Hmm, I just took a quick look at the Karateka and it is hitting these softswitches (low byte stored $1020 .. $1028):

1000:A0 00 LDY #$00
1015:BC 20 10 LDY $1020,X
1018:99 00 C0 STA KEYBOARD,Y
101B:F0 0D BEQ $102A
101D:E8 INX
101E:D0 F5 BNE $1015
1020:5F B_1020 db #$5F
1021:57 B_1021 db #$57
1022:52 B_1022 db #$52
1023:54 B_1023 db #$54
1024:50 B_1024 db #$50
1025:0C B_1025 db #$0C
1026:08 B_1026 db #$08
1027:04 B_1027 db #$04
1028:02 B_1028 db #$02
1029:00 B_1029 db #$00
102A:A6 2B LDX BAS2H

i.e.
C05F
C057
C052
C054
C050
C00C
C008
C004
C002

Might be a place to start?
Post by Jens Gaulke
When testing via AppleSoft. Second topic: booting a Disk is slooooow. I have no idea how to Speed that up at the Moment.
In AppleWin check the variable: bool m_enhanceDisk

It looks like we omit the spinning up time in Disk2InterfaceCard::ReadWrite():
i.e.
if (!m_enhanceDisk && pDrive->m_spinning)
Post by Jens Gaulke
That's how it is going at the moment. Thank you all for your support!
Thanks for the update!

Michael
peter....@gmail.com
2023-04-01 19:37:09 UTC
Permalink
DOS turns off the drive on exit, and skips spin-up if the parameters are the same on the next call and the disk appears to be still spinning. Maybe that's causing the read failure?
I am Rob
2023-04-03 00:29:25 UTC
Permalink
Might be the order in which the soft switches are accessed. Sweet16 has the same problem. $C050 has to be accessed before any other softswitch.
Steve Nickolas
2023-04-03 04:41:23 UTC
Permalink
Post by I am Rob
Post by Jens Gaulke
There are just two things that bother me. Booting up karateka does not
switch to graphics mode correctly though I Implemented all the
softswitches and they work correctly When testing via AppleSoft. Second
Might be the order in which the soft switches are accessed. Sweet16 has
the same problem. $C050 has to be accessed before any other softswitch.
For what it's worth, I frequently hit them from the bottom up, C057, C054,
C052, C050.

I just looked at one of my file cracks: it says that it relies on the
firmware to hit C052 and C054 (in no defined order, but fwiw, it's the
FE84-FE89-FE93-FB2F dance you see some variation of in a lot of software)
and then it hits C057, and only some time later hits C050.

So you're going to need to be prepared for the softswitches to be hit in
any arbitrary order.

-uso.
Jens Gaulke
2023-04-03 20:23:55 UTC
Permalink
Hi all,

the errors in the graphical representation were due to the fact that I overestimated the performance of Python. I refreshed the screen completely every 20 ms, as I know it from game programming. The setting of all pixels including the calculation of the color values blew up these 20ms. I now use a surfarray3d and set the respective pixel when it is set in RAM. Then I flash every 20ms the content of the surfarray3d on the screen. for this I use a surfarray3d for hires1 and one for hires2. Depending on the softswitch I flash the corresponding surfarray3d. This works surprisingly well. What I don't like so much is that my object-oriented structure has suffered a bit.

That sometimes softswitches are set wildly and I have to take this into account in the graphics output, I have noticed in the meantime. I try to map this behavior by my new graphics routines.

Now that Karateka runs graphically, I took care of the joystick implementation. In most cases this works as well.

Actually I just wanted to resurrect my old Apple II, but here one more function and there one more function... that's really fun.

Thank you all for your input and the exchange, I really appreciate that!
Jens Gaulke
2023-04-03 20:31:21 UTC
Permalink
I also got the speed problem with the floppy drive under control.

first i restructured the gameloop and then realized that i was providing each sector "on demand" in the DSK routine. but since some sectors are read multiple times, i was wasting clock cycles here. I then converted the DSK file completely, stored it in an array and access the array on demand.

Stupid me

Loading...