diff --git a/docs/source/disk.rst b/docs/source/disk.rst index 6cf5b16..12a6e35 100644 --- a/docs/source/disk.rst +++ b/docs/source/disk.rst @@ -1,6 +1,35 @@ +File System +=========== +(Mostly unconfirmed) +By tracing CPU instructions and doing annotated disassembly I have +made the following colclusions and assumptions. +The disk consists of 77 tracks (0 - 76), each track having 2468 bytes. I've +decided that the 'index' position is located at the first byte of each +track. I assume that when selecting a disk Track 0 is the first track. + +The disk controller issues a 'step' and a 'step direction' command. +In the documentation (Q1 ASM IO addresess p. 80), Bit 5 is the step command +and Bit 6 the 'direction' where '1' is UP and 0 (assumed) DOWN. However +I have had to reverse this logic. Perhaps I should have started at Track 76? + +I assume that the byte offset within a track is increasing on every read and +also that it wraps around to 0 when reading past the last byte. + +In order to initialise the file system I fill out 55 records on each track +with information compatible with figure2 on page 17 (same document): + +Each record looks like this + +|0x9e|Trk|Sect|Csum|0xa|x00 x00 x00 x00 x00 x00|0x9b|Trk|Sect|Csum|0xa|1234|x00 x00 x00 x00 x00 x00| + +So far I have seen no definition of what a sector is. So I am assuming the above +record is two sectors, one starting with 0x9e (ID Record) and one starting with 0x9b +'(Data Record)'. + +This clearly need more work, but it is a start. Index File ========== diff --git a/docs/source/hooks.rst b/docs/source/hooks.rst index a20e6a6..8be94ca 100644 --- a/docs/source/hooks.rst +++ b/docs/source/hooks.rst @@ -87,8 +87,11 @@ file descriptors. disk ^^^^ -The **disk** module is in its infancy. But the aim is to emulate a -spinning disk with loadable files on it. +The **disk** module provides a **Disk** class and a **Disk Control** class. +The **control** class is used by the **z80io** module to perform disk functions +during the registered io callback functions. The +**disk** module holds the file system data, can move between tracks, +read (but not yet write) data and respond to status commands. Applications diff --git a/src/disk.py b/src/disk.py index 6472f49..40da082 100644 --- a/src/disk.py +++ b/src/disk.py @@ -1,24 +1,115 @@ +import sys from enum import Enum # for now assume disk 1 only, drive 1 only, side 0 only +class Disk: + def __init__(self, disk, tracks=77, bpt=2468): # + self.disk = disk + self.Tracks = tracks + self.BytesPerTrack = bpt + self.data = [[0x00 for B in range(self.BytesPerTrack)] for A in range(self.Tracks)] + self.CurrentTrack = 0 + self.CurrentByte = 0 + self.BytesRead = 0 + for track in range(77): + for block in range(55): + step = block * 26 + self.data[track][step + 0] = 0x9e # ID Record + self.data[track][step + 1] = track + self.data[track][step + 2] = block * 2 # sector + self.data[track][step + 3] = 0x56 # cksum + self.data[track][step + 4] = 10 # + self.data[track][step + 5] = 0 # GAP + self.data[track][step + 6] = 0 # + self.data[track][step + 7] = 0 # + self.data[track][step + 8] = 0 # + self.data[track][step + 9] = 0 # + self.data[track][step + 10] = 0 # + + self.data[track][step + 11] = 0x9b # Data Record + self.data[track][step + 12] = track + self.data[track][step + 13] = block * 2 + 1 + self.data[track][step + 14] = 0x56 # cksum + self.data[track][step + 15] = 10 + + self.data[track][step + 16] = 0x31 + self.data[track][step + 17] = 0x32 + self.data[track][step + 18] = 0x33 + self.data[track][step + 19] = 0x34 + + self.data[track][step + 20] = 0 # GAP + self.data[track][step + 21] = 0 # + self.data[track][step + 22] = 0 # + self.data[track][step + 23] = 0 # + self.data[track][step + 24] = 0 # + self.data[track][step + 25] = 0 # + + + + def step(self, direction): + print(f'disk {self.disk}, step {direction}, track {self.CurrentTrack}') + self.CurrentByte = 0 + if not direction: # DOWN + if self.CurrentTrack == 77: + return + self.CurrentTrack += 1 + else: # UP + if self.CurrentTrack == 0: + return + self.CurrentTrack -= 1 + + + def readbyte(self): + track = self.CurrentTrack + byte = self.CurrentByte + #print(f'readbyte: disk {self.disk} track {self.CurrentTrack} byte {self.CurrentByte} value {self.data[track][byte]:02x}') + assert 0 <= track < self.Tracks + assert 0 <= byte < self.BytesPerTrack + if self.CurrentByte < self.BytesPerTrack: + self.CurrentByte += 1 + if self.CurrentByte == self.BytesPerTrack: # wrap + self.CurrentByte = 0 + self.BytesRead += 1 + return self.data[track][byte] + + + def gettrackno(self): + return self.CurrentTrack + + + def isbusy(self): + if self.CurrentByte == 0 or self.BytesRead > 2468: + return True + return False + + +doubleside = 0x02 +track0 = 0x10 +index = 0x20 +sdready = 0x40 +busy = 0x80 + class Control: - def Status(Enum): - doubleside = 0x02 - track0 = 0x10 - index = 0x20 - sdready = 0x40 - busy = 0x80 - - def __init__(self): + def __init__(self, diskno): self.track0 = 0 - self.pos = 0 + self.trackdir = 0 self.write = 0 - self.data = "AaBbCcDd" + self.disk = Disk(diskno) + + + def data_in(self) -> int: + val = self.disk.readbyte() + # print(f'disk {self.disk.disk}, track {self.disk.CurrentTrack}, ' + \ + # f'byte {self.disk.CurrentByte}, val {val:02x}, count {self.disk.BytesRead}') + return val + def control1(self, val): + if val == 0: + return side = val >> 7 drive = (val & 0x7f) assert drive in [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40] @@ -30,8 +121,27 @@ def control1(self, val): i += 1 assert 1 <= drive <= 7 assert 0 <= side <= 1 + assert drive == 1 + def control2(self, val): + stepdir = val & 0x40 + if val & 0x20: # Step + self.disk.step(stepdir) + if val & 0x80: + self.write = 1 def status(self): - return Status.sdready + Status.index + Status.track0 + track = self.disk.CurrentTrack + status = sdready # + index + if self.disk.isbusy(): + status += busy + if track == 0: + status += track + #print(f'disk {self.disk.disk}, track {track}, CurrByte {self.disk.CurrentByte}, TotBytes {self.disk.BytesRead}, status {status:02x}') + return status + + +if __name__ == "__main__": + d = Disk(1) + print(d.data) diff --git a/src/emulator.py b/src/emulator.py index 4431f06..2dac5e2 100644 --- a/src/emulator.py +++ b/src/emulator.py @@ -63,17 +63,10 @@ def main(args): if not args.nodecode: print(inst_str2, annot) - # IO hook for output - # if bytes[0] == 0xD3: - # io.handle_io_out(bytes[1], cpu.m.a) if icount % args.dumpfreq == 0 and not args.nodump: cpu.mem.hexdump(0x2000, 0x10000 - 0x2000, icount) # dump RAM part of memory - # if pc == 0x0818: # Break Point/Trigger point - # cpu.mem.hexdump(0x2000, 0x10000 - 0x2000, icount) - # args.nodecode = False - # main cpu emulation step cpu.step() # does the actual emulation of the next instruction @@ -120,6 +113,10 @@ def main(args): int38(cpu, io, 0x1b) # opt-c -> CLEAR ENTRY elif ch == 181: # opt-m -> INSERT MODE int38(cpu, io, 0x1e) + elif ch == 170: # opt-a FDs + ros.index() + ros.file() + ros.disk() else: int38(cpu, io, ch) @@ -140,7 +137,7 @@ def auto_int(x): parser.add_argument("-s", "--stopafter", help = "stop after N instructions", type = int, default = -1) parser.add_argument("-p", "--poi", help = "Point of interest (PC)", - type = auto_int, default = 0x0d8a) + type = auto_int, default = 0x1ffff) parser.add_argument("--dumpfreq", help = "Hexdump every N instruction", type = int, default = 256) parser.add_argument("-n", "--nodump", help = "Toggle hexdump", action='store_true') diff --git a/src/progs/jdc.py b/src/progs/jdc.py index a55bf16..d8142de 100644 --- a/src/progs/jdc.py +++ b/src/progs/jdc.py @@ -114,6 +114,9 @@ 0x0dc5: 'print: "INDEX"', 0x0dc8: 'STOP', 0x0ddd: 'A=4 - fourth err msg: "NOT FOUND"', + 0x0fea: '# records = 0x58 (88)', + 0x0fef: 'Records per Track = 0x82 (130)', + 0x0ff8: 'Record Length = 0x28 (24)', 0x130b: 'drive 1, track# = 255', 0x1319: 'get disk status', 0x131b: 'check if at track 0', @@ -124,11 +127,17 @@ 0x138d: 'read byte from disk', 0x1397: 'store track# for drive 1', 0x139e: 'check if ID record (0x9e)', + 0x13e8: 'check if at index 0', + 0x13ec: 'Track step bit', + 0x13fb: 'Double density?', + 0x13ff: 'l = 77 (number of tracks?)', 0x13ce: 'select drive (and side)', 0x140e: 'read byte from disk', 0x1411: 'read byte from disk', 0x1414: 'read byte from disk', 0x1417: 'read byte from disk', + 0x141e: 'set ERC to 10', + 0x145a: 'Data Record (0x9b)', 0x146a: 'get disk status', 0x146c: 'track 0 selected', 0x1482: 'get disk status', @@ -212,7 +221,7 @@ [0x0d8e, 0x0da6, 'report()'], [0x0da7, 0x0de0, 'print nth error message'], [0x0de1, 0x0e4e, 'text strings - INDEX .. WEIRD ERR'], - [0x0e4f, 0x0f4f, 'key search()'], + [0x0e4f, 0x0fe0, 'key search()'], [0x0fe1, 0x0fff, '???'], # 1000 - 17ff marked as unused in ROS Manual! [0x1003, 0x1005, 'write?'], diff --git a/src/z80io.py b/src/z80io.py index 2f46aad..33ce304 100644 --- a/src/z80io.py +++ b/src/z80io.py @@ -12,7 +12,8 @@ def isprintable(c): class IO: def __init__(self, m): - self.disk = disk.Control() + self.disk1 = disk.Control(1) + self.disk2 = disk.Control(2) self.m = m self.incb = {} self.outcb = {} @@ -162,10 +163,15 @@ def handle_printer_out_7(self, val): def handle_disk_out_0a(self, val): if val != 0: self.print(f'IO out - disk1 (control 1 ) - (0x{val:02x})') + self.disk1.control1(val) def handle_disk_out_0b(self, val): if val != 0: self.print(f'IO out - disk1 (control 2 ) - (0x{val:02x})') + self.disk1.control2(val) + # if val & 0x20: + # print(f'XXX track step at {self.m.pc:04x}') + @@ -173,31 +179,14 @@ def handle_disk_out_09(self, val): self.print(f'IO out - disk1 (data) - (0x{val:02x})') - # def handle_ciu_out_0b(self, val): - # self.print(f'IO out - CIU/DISK? (control 1) - (0x{val:02x})') - - - # def handle_ciu_out_0c(self, val): - # if val == 0x81: - # desc = 'synchronous mode, master reset' - # else: - # desc = 'unspecified' - # self.print(f'IO out - CIU/DISK? (control 2) - {desc} (0x{val:02x})') - - - # def handle_disk_0c(self): - # self.print('IO in - CIU/DISK? (0xc) (control 1): 0x00') - # return 0 - def handle_disk_in_0a(self): - self.print(f'IO in - disk1 (0xa) (status): {self.diskstatus}') - return self.diskstatus # for now + retval = self.disk1.status() + self.print(f'IO in - disk1 (0xa) (status): 0x{retval:02x}') + return retval def handle_disk_in_09(self): - retval = self.diskdata - self.print(f'IO in - disk1 (0x9) (data): {retval}') - - self.diskdata = 0 + retval = self.disk1.data_in() + self.print(f'IO in - disk1 (0x9) (data): 0x{retval:02x}') return retval # possibly not disk @@ -208,19 +197,23 @@ def handle_disk_in_0c(self): ### Disk control (from Q1 Assembler p. 52) def handle_disk_in_19(self): - self.print(f'IO in - disk2 (data): {self.diskdata}') - return self.diskdata + retval =self.disk2.data_in() + self.print(f'IO in - disk2 (data): {retval}') + return retval def handle_disk_in_1a(self): - self.print(f'IO in - disk2 (status): {self.diskstatus}') - return self.diskstatus + retval = self.disk2.status() + self.print(f'IO in - disk2 (status): {retval}') + return retval def handle_disk_out_1a(self, val): if val != 0: self.print(f'IO out - disk2 (control 1 ) - (0x{val:02x})') + self.disk2.control1(val) def handle_disk_out_1b(self, val): if val != 0: self.print(f'IO out - disk2 (control 2 ) - (0x{val:02x})') + self.disk2.control2(val)