Skip to content

Commit

Permalink
Initial micropolis support
Browse files Browse the repository at this point in the history
  • Loading branch information
keirf committed Oct 3, 2024
1 parent 3d723b7 commit 7e1ae39
Show file tree
Hide file tree
Showing 5 changed files with 311 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/greaseweazle/codec/codec.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ def __init__(self, name: Optional[str],
from greaseweazle.codec.apple2 import apple2_gcr
from greaseweazle.codec.hp import hp_mmfm
from greaseweazle.codec.northstar import northstar
from greaseweazle.codec.micropolis import micropolis

def mk_trackdef(format_name: str) -> TrackDef:
if format_name in ['amiga.amigados']:
Expand All @@ -174,6 +175,8 @@ def mk_trackdef(format_name: str) -> TrackDef:
return hp_mmfm.HPMMFMDef(format_name)
if format_name in ['northstar']:
return northstar.NorthStarDef(format_name)
if format_name in ['micropolis']:
return micropolis.MicropolisDef(format_name)
if format_name in ['apple2.gcr']:
return apple2_gcr.Apple2GCRDef(format_name)
if format_name in ['bitcell']:
Expand Down
Empty file.
234 changes: 234 additions & 0 deletions src/greaseweazle/codec/micropolis/micropolis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
# greaseweazle/codec/hp/micropolis.py
#
# Written & released by Keir Fraser <[email protected]>
#
# This is free and unencumbered software released into the public domain.

from typing import List, Optional

import struct
from bitarray import bitarray
import crcmod.predefined
import itertools as it
from enum import Enum

from greaseweazle import error
from greaseweazle.codec import codec
from greaseweazle.codec.ibm.ibm import decode, encode, fm_encode, mfm_encode
from greaseweazle.track import MasterTrack, PLL, PLLTrack
from greaseweazle.flux import HasFlux

default_revs = 1

bad_sector = b'-=[BAD SECTOR]=-'

mfm_sync = bitarray(endian='big')
mfm_sync.frombytes(mfm_encode(encode(b'\x00\x00\x00\xff')))

# Sector format is:
# 40: 00 preamble
# 1: FF sync
# 2: track, sector
# 10: padding/vendor
# 256: payload
# 1: checksum
# 5: ecc [optional]
# NN: 00 postamble

def micropolis_csum(dat):
y = 0
for x in dat:
if y > 255:
y -= 255
y += x
# Ignore carry on last addition
return y & 255

class Micropolis(codec.Codec):

time_per_rev = 0.2

verify_revs: float = default_revs

def __init__(self, cyl: int, head: int, config):
self.clock = 2e-6
self.presync_bytes = 40
self.cyl, self.head = cyl, head
self.config = config
self.sector: List[Optional[bytes]]
self.sector = [None] * self.nsec

@property
def nsec(self) -> int:
return self.config.secs

@property
def img_bps(self) -> int:
return self.config.img_bps

def summary_string(self) -> str:
nsec, nbad = self.nsec, self.nr_missing()
s = "Micropolis (%d/%d sectors)" % (nsec - nbad, nsec)
return s

def bad_sector(self) -> bytes:
if self.img_bps == 256:
return bad_sector * 16
return b'\xff' + bytes(12) + bad_sector*16 + bytes(6)

# private
def add(self, sec_id, data) -> None:
assert not self.has_sec(sec_id)
self.sector[sec_id] = data

def has_sec(self, sec_id: int) -> bool:
return self.sector[sec_id] is not None

def nr_missing(self) -> int:
return len([sec for sec in self.sector if sec is None])

def get_img_track(self) -> bytearray:
tdat = bytearray()
for sec in self.sector:
tdat += sec if sec is not None else self.bad_sector()
return tdat

def set_img_track(self, tdat: bytes) -> int:
totsize = self.nsec * self.img_bps
if len(tdat) < totsize:
tdat += bytes(totsize - len(tdat))
for sec in range(self.nsec):
self.sector[sec] = tdat[sec*self.img_bps:(sec+1)*self.img_bps]
return totsize

def decode_flux(self, track: HasFlux, pll: Optional[PLL]=None) -> None:
flux = track.flux()
if flux.time_per_rev < self.time_per_rev / 2:
flux.identify_hard_sectors()
flux.cue_at_index()
raw = PLLTrack(time_per_rev = self.time_per_rev,
clock = self.clock, data = flux, pll = pll)

for rev in range(len(raw.revolutions)):

if self.nr_missing() == 0:
break

bits, _ = raw.get_revolution(rev)

hardsector_bits = raw.revolutions[rev].hardsector_bits
if hardsector_bits is not None:
hardsector_bits = list(it.accumulate(hardsector_bits))
else:
hardsector_bits = [len(bits)*(i+1)//self.nsec
for i in range(self.nsec)]
error.check(len(hardsector_bits) == self.nsec,
f'North Star: Unexpected number of sectors: '
f'{len(hardsector_bits)}')
hardsector_bits = [0] + hardsector_bits

for hsec_id in range(self.nsec):

s, e = hardsector_bits[hsec_id], hardsector_bits[hsec_id+1]
# Skip first 50 cells (~100us) to avoid false sync
# Reference: Vector Micropolis Disk Controller Board Technical
# Information Manual, pp. 1-16.
s += 50
offs = bits[s:e].search(mfm_sync)
for off in offs:
off += 3*16
dat = decode(bits[s+off:s+off+275*16].tobytes())
if len(dat) != 275:
continue
cyl, sec_id = dat[1], dat[2]
if cyl != self.cyl or sec_id > self.nsec:
print('T%d.%d: Ignoring unexpected sector C:%d R:%d'
% (self.cyl, self.head, cyl, sec_id))
continue
if self.has_sec(sec_id):
continue
if micropolis_csum(dat[1:-6]) == dat[-6]:
if hsec_id != sec_id:
print(f'T{self.cyl, self.head}: Weird skew: '
f'hsec {hsec_id} contains sector {sec_id}')
if self.img_bps == 256:
self.add(sec_id, dat[13:13+256])
else:
self.add(sec_id, dat)
break # We are done on this hard sector


def master_track(self) -> MasterTrack:

t = bytes()
slen = int((self.time_per_rev / self.clock / self.nsec / 16))

for sec_id in range(self.nsec):
s = encode(bytes(self.presync_bytes))
sector = self.sector[sec_id]
sector = self.bad_sector() if sector is None else sector
if len(sector) == 256:
dat = b'\xff' + bytes([self.cyl, sec_id]) + bytes(10)
dat += sector
dat += bytes([micropolis_csum(dat[1:])])
else:
if sector[0] != 0xff:
print("T%u.%u: 275-byte sector doesn't start with FF sync"
% (self.cyl, self.head))
dat = sector
s += encode(dat)
s += encode(bytes(slen - len(s)//2))
t += s

t = mfm_encode(t)

hardsector_bits = [slen*16*i for i in range(self.nsec)]

track = MasterTrack(bits = t, time_per_rev = self.time_per_rev)
track.verify = self
return track


def verify_track(self, flux):
readback_track = self.__class__(self.cyl, self.head, self.config)
readback_track.decode_flux(flux)
return (readback_track.nr_missing() == 0
and self.sector == readback_track.sector)


class MicropolisDef(codec.TrackDef):

default_revs = default_revs

def __init__(self, format_name: str):
self.secs: Optional[int] = None
self.img_bps: Optional[int] = None
self.finalised = False

def add_param(self, key: str, val) -> None:
if key == 'secs':
val = int(val)
self.secs = val
elif key == 'img_bps':
val = int(val)
self.img_bps = val
error.check(val in [256, 275], f'bad img_bps {val}')
else:
raise error.Fatal('unrecognised track option %s' % key)

def finalise(self) -> None:
if self.finalised:
return
error.check(self.secs is not None,
'number of sectors not specified')
error.check(self.img_bps is not None,
'img_bps not specified')
self.finalised = True

def mk_track(self, cyl: int, head: int) -> Micropolis:
return Micropolis(cyl, head, self)


# Local variables:
# python-indent: 4
# End:
1 change: 1 addition & 0 deletions src/greaseweazle/data/diskdefs.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import gem. "diskdefs_gem.cfg"
import hp. "diskdefs_hp.cfg"
import ibm. "diskdefs_ibm.cfg"
import mac. "diskdefs_mac.cfg"
import micropolis. "diskdefs_micropolis.cfg"
import mm1. "diskdefs_mm1.cfg"
import msx. "diskdefs_msx.cfg"
import northstar. "diskdefs_northstar.cfg"
Expand Down
73 changes: 73 additions & 0 deletions src/greaseweazle/data/diskdefs_micropolis.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# prefix: micropolis.

disk 48tpi.ss
cyls = 35
heads = 1
tracks * micropolis
secs = 16
img_bps = 256
end
end

disk 48tpi.ds
cyls = 35
heads = 2
tracks * micropolis
secs = 16
img_bps = 256
end
end

disk 48tpi.ss.275
cyls = 35
heads = 1
tracks * micropolis
secs = 16
img_bps = 275
end
end

disk 48tpi.ds.275
cyls = 35
heads = 2
tracks * micropolis
secs = 16
img_bps = 275
end
end

disk 100tpi.ss
cyls = 77
heads = 1
tracks * micropolis
secs = 16
img_bps = 256
end
end

disk 100tpi.ds
cyls = 77
heads = 2
tracks * micropolis
secs = 16
img_bps = 256
end
end

disk 100tpi.ss.275
cyls = 77
heads = 1
tracks * micropolis
secs = 16
img_bps = 275
end
end

disk 100tpi.ds.275
cyls = 77
heads = 2
tracks * micropolis
secs = 16
img_bps = 275
end
end

0 comments on commit 7e1ae39

Please sign in to comment.