Skip to content
This repository has been archived by the owner on Aug 20, 2024. It is now read-only.

Commit

Permalink
doc, disk abstraction, cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
mortenjc committed Jul 22, 2024
1 parent 967b833 commit 85eda49
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 49 deletions.
29 changes: 29 additions & 0 deletions docs/source/disk.rst
Original file line number Diff line number Diff line change
@@ -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
==========
Expand Down
7 changes: 5 additions & 2 deletions docs/source/hooks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
132 changes: 121 additions & 11 deletions src/disk.py
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -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)
13 changes: 5 additions & 8 deletions src/emulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)

Expand All @@ -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')
Expand Down
11 changes: 10 additions & 1 deletion src/progs/jdc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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',
Expand Down Expand Up @@ -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?'],
Expand Down
47 changes: 20 additions & 27 deletions src/z80io.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand Down Expand Up @@ -162,42 +163,30 @@ 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}')




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
Expand All @@ -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)

0 comments on commit 85eda49

Please sign in to comment.