Support for 13 sectors disks

This commit is contained in:
Iván Izaguirre 2024-07-21 21:45:04 +02:00
parent ad720f0527
commit 20e18df2e7
23 changed files with 212 additions and 60 deletions

View File

@ -15,6 +15,9 @@ Portable emulator of an Apple II+ or //e. Written in Go.
- DSK
- PO
- [WOZ 1.0 or 2.0](storage/WozSupportStatus.md) (read only)
- 13 Sector 5 1/4 diskettes. Uncompressed or compressed witth gzip or zip. Supported formats:
- NIB (read only)
- [WOZ 2.0](storage/WozSupportStatus.md) (read only)
- 3.5 disks in PO or 2MG format
- Hard disk in HDV or 2MG format with ProDOS and SmartPort support
- Emulated extension cards:
@ -225,6 +228,7 @@ The available pre-configured models are:
2enh: Apple //e
2plus: Apple ][+
base64a: Base 64A
dos32: Apple ][+ with 13 sectors disk adapter and DOS 3.2x
swyft: swyft
The available cards are:

View File

@ -25,6 +25,8 @@ NIB: 35 tracks 6656 bytes, 232960 bytes
// CardDisk2 is a DiskII interface card
type CardDisk2 struct {
cardBase
sectors13 bool
selected int // q5, Only 0 and 1 supported
power bool // q4
drive [2]cardDisk2Drive
@ -57,13 +59,11 @@ func newCardDisk2Builder() *cardBuilder {
{"disk2", "Diskette image for drive 2", ""},
{"tracktracer", "Trace how the disk head moves between tracks", "false"},
{"fast", "Enable CPU burst when accessing the disk", "true"},
{"sectors13", "Use 13 sectors per track ROM", "false"},
},
buildFunc: func(params map[string]string) (Card, error) {
var c CardDisk2
err := c.loadRomFromResource("<internal>/DISK2.rom", cardRomSimple)
if err != nil {
return nil, err
}
c.sectors13 = paramsGetBool(params, "sectors13")
disk1 := paramsGetPath(params, "disk1")
if disk1 != "" {
@ -71,6 +71,10 @@ func newCardDisk2Builder() *cardBuilder {
if err != nil {
return nil, err
}
if c.drive[0].diskette.Is13Sectors() && !c.sectors13 {
// Auto configure for 13 sectors per track
c.sectors13 = true
}
}
disk2 := paramsGetPath(params, "disk2")
if disk2 != "" {
@ -79,6 +83,17 @@ func newCardDisk2Builder() *cardBuilder {
return nil, err
}
}
P5RomFile := "<internal>/Apple Disk II 16 Sector Interface Card ROM P5 - 341-0027.bin"
if c.sectors13 {
P5RomFile = "<internal>/Apple Disk II 13 Sector Interface Card ROM P5 - 341-0009.bin"
}
err := c.loadRomFromResource(P5RomFile, cardRomSimple)
if err != nil {
return nil, err
}
trackTracer := paramsGetBool(params, "tracktracer")
if trackTracer {
c.trackTracer = makeTrackTracerLogger()
@ -92,7 +107,11 @@ func newCardDisk2Builder() *cardBuilder {
// GetInfo returns smartPort info
func (c *CardDisk2) GetInfo() map[string]string {
info := make(map[string]string)
info["rom"] = "16 sector"
if c.sectors13 {
info["rom"] = "13 sector"
} else {
info["rom"] = "16 sector"
}
info["power"] = strconv.FormatBool(c.power)
info["D1 name"] = c.drive[0].name

View File

@ -22,7 +22,9 @@ See:
type CardDisk2Sequencer struct {
cardBase
p6ROM []uint8
sectors13 bool
p6ROM []uint8
q [8]bool // 8-bit latch SN74LS259
register uint8 // 8-bit shift/storage register SN74LS323
sequence uint8 // 4 bits stored in an hex flip-flop SN74LS174
@ -66,16 +68,6 @@ func newCardDisk2SequencerBuilder() *cardBuilder {
},
buildFunc: func(params map[string]string) (Card, error) {
var c CardDisk2Sequencer
err := c.loadRomFromResource("<internal>/DISK2.rom", cardRomSimple)
if err != nil {
return nil, err
}
data, _, err := LoadResource("<internal>/DISK2P6.rom")
if err != nil {
return nil, err
}
c.p6ROM = data
disk1 := paramsGetString(params, "disk1")
if disk1 != "" {
@ -83,7 +75,9 @@ func newCardDisk2SequencerBuilder() *cardBuilder {
if err != nil {
return nil, err
}
c.sectors13 = c.drive[0].data.Info.BootSectorFormat == 2 // Woz 13 sector disk
}
disk2 := paramsGetString(params, "disk2")
if disk2 != "" {
err := c.drive[1].insertDiskette(disk2)
@ -91,6 +85,26 @@ func newCardDisk2SequencerBuilder() *cardBuilder {
return nil, err
}
}
P5RomFile := "<internal>/Apple Disk II 16 Sector Interface Card ROM P5 - 341-0027.bin"
P6RomFile := "<internal>/Apple Disk II 16 Sector Interface Card ROM P6 - 341-0028.bin"
if c.sectors13 {
P5RomFile = "<internal>/Apple Disk II 13 Sector Interface Card ROM P5 - 341-0009.bin"
// Buggy sequencer not need for 13 sectors disks to work
//P6RomFile = "<internal>/Apple Disk II 13 Sector Interface Card ROM P6 - 341-0010.bin"
}
err := c.loadRomFromResource(P5RomFile, cardRomSimple)
if err != nil {
return nil, err
}
data, _, err := LoadResource(P6RomFile)
if err != nil {
return nil, err
}
c.p6ROM = data
trackTracer := paramsGetBool(params, "tracktracer")
if trackTracer {
c.trackTracer = makeTrackTracerLogger()
@ -103,7 +117,11 @@ func newCardDisk2SequencerBuilder() *cardBuilder {
// GetInfo returns card info
func (c *CardDisk2Sequencer) GetInfo() map[string]string {
info := make(map[string]string)
info["rom"] = "16 sector"
if c.sectors13 {
info["rom"] = "13 sector"
} else {
info["rom"] = "16 sector"
}
// TODO: add drives info
return info
}

View File

@ -34,9 +34,6 @@ func (d *cardDisk2SequencerDrive) insertDiskette(filename string) error {
if f.Info.DiskType != 1 {
return errors.New("only 5.25 disks are supported")
}
if f.Info.BootSectorFormat == 2 { // Info not available in WOZ 1.0
return errors.New("woz 13 sector disks are not supported")
}
d.data = f
d.writeProtected = !writeable

4
configs/dos32.cfg Normal file
View File

@ -0,0 +1,4 @@
name: Apple ][ with 13 sectors disk adapter and DOS 3.2x
parent: 2
s0: multirom,bank=7,basic=0
s6: diskii,sectors13,disk1=<internal>/dos32.nib

View File

@ -63,9 +63,13 @@ func (c *configuration) getHas(key string) (string, bool) {
return value, ok
}
func (c *configuration) has(key string) bool {
_, ok := c.getHas(key)
return ok
}
func (c *configuration) get(key string) string {
key = strings.ToLower(key)
value, ok := c.data[key]
value, ok := c.getHas(key)
if !ok {
// Should not happen
panic(fmt.Errorf("key %s not found", key))

View File

@ -50,6 +50,7 @@ The available pre-configured models are:
2enh: Apple //e
2plus: Apple ][+
base64a: Base 64A
dos32: Apple ][+ with 13 sectors disk adapter and DOS 3.2x
swyft: swyft
The available cards are:

View File

@ -5,11 +5,17 @@ import (
"testing"
)
func testBoots(t *testing.T, model string, disk string, cycles uint64, banner string, prompt string, col80 bool) {
overrides := newConfiguration()
func testBoots(t *testing.T, model string, disk string, overrides *configuration, cycles uint64, banner string, prompt string, col80 bool) {
if overrides == nil {
overrides = newConfiguration()
}
if disk != "" {
if overrides.has(confS6) {
t.Fatal("Do not set custom slot 6 configuration and custom disk")
}
overrides.set(confS6, "diskii,disk1=\""+disk+"\"")
} else {
} else if !overrides.has(confS6) {
overrides.set(confS6, "empty")
}
@ -36,29 +42,36 @@ func testBoots(t *testing.T, model string, disk string, cycles uint64, banner st
}
func TestPlusBoots(t *testing.T) {
testBoots(t, "2plus", "", 200_000, "APPLE ][", "\n]", false)
testBoots(t, "2plus", "", nil, 200_000, "APPLE ][", "\n]", false)
}
func Test2EBoots(t *testing.T) {
testBoots(t, "2e", "", 200_000, "Apple ][", "\n]", false)
testBoots(t, "2e", "", nil, 200_000, "Apple ][", "\n]", false)
}
func Test2EnhancedBoots(t *testing.T) {
testBoots(t, "2enh", "", 200_000, "Apple //e", "\n]", false)
testBoots(t, "2enh", "", nil, 200_000, "Apple //e", "\n]", false)
}
func TestBase64Boots(t *testing.T) {
testBoots(t, "base64a", "", 1_000_000, "BASE 64A", "\n]", false)
testBoots(t, "base64a", "", nil, 1_000_000, "BASE 64A", "\n]", false)
}
func TestPlusDOS32Boots(t *testing.T) {
overrides := newConfiguration()
overrides.set(confS0, "multirom,bank=7,basic=0")
overrides.set(confS6, "diskii,sectors13,disk1=<internal>/dos32.nib")
testBoots(t, "2plus", "", overrides, 100_000_000, "MASTER DISKETTE VERSION 3.2 STANDARD", "\n>", false)
}
func TestPlusDOS33Boots(t *testing.T) {
testBoots(t, "2plus", "<internal>/dos33.dsk", 100_000_000, "DOS VERSION 3.3", "\n]", false)
testBoots(t, "2plus", "<internal>/dos33.dsk", nil, 100_000_000, "DOS VERSION 3.3", "\n]", false)
}
func TestProdDOSBoots(t *testing.T) {
testBoots(t, "2enh", "<internal>/ProDOS_2_4_3.po", 100_000_000, "BITSY BYE", "NEW VOL", false)
testBoots(t, "2enh", "<internal>/ProDOS_2_4_3.po", nil, 100_000_000, "BITSY BYE", "NEW VOL", false)
}
func TestCPM65Boots(t *testing.T) {
testBoots(t, "2enh", "<internal>/cpm65.po", 5_000_000, "CP/M-65 for the Apple II", "\nA>", true)
testBoots(t, "2enh", "<internal>/cpm65.po", nil, 5_000_000, "CP/M-65 for the Apple II", "\nA>", true)
}

View File

@ -55,9 +55,9 @@ func TestWoz(t *testing.T) {
cycleLimit uint64
expectedTracks []int
}{
// How to being
// DOS 3.2, requires 13 sector disks
{"DOS3.3", all, "DOS 3.3 System Master.woz", 11_000_000, []int{0, 8, 0, 76, 68, 84, 68, 84, 68, 92, 16, 24}},
// How to begin
{"DOS 3.2", all, "DOS 3.2 System Master.woz", 7_000_000, []int{0, 72}},
{"DOS 3.3", all, "DOS 3.3 System Master.woz", 11_000_000, []int{0, 8, 0, 76, 68, 84, 68, 84, 68, 92, 16, 24}},
// Next choices
{"Bouncing Kamungas", all, "Bouncing Kamungas - Disk 1, Side A.woz", 30_000_000, []int{0, 32, 0, 40, 0}},

View File

@ -53,7 +53,7 @@ const (
nscStateEnabled = uint8(2)
)
func newNoSlotClockDS1216(a *Apple2, memory memoryHandler) *noSlotClockDS1216 {
func newNoSlotClockDS1216(_ *Apple2, memory memoryHandler) *noSlotClockDS1216 {
var nsc noSlotClockDS1216
nsc.memory = memory
nsc.state = nscStateDisabled

View File

@ -0,0 +1,65 @@
ИИ<EFBFBD>Ш
<EFBFBD>Щ<EFBFBD>Щ<EFBFBD>Ы<EFBFBD>Ы<EFBFBD>ШШ
<EFBFBD>Щ<EFBFBD>Щ<EFBFBD>Ы<EFBFBD>ЫИ=ИИ
<EFBFBD>й<EFBFBD>й<EFBFBD>л<EFBFBD>л<EFBFBD>н<EFBFBD>и
<EFBFBD>й<EFBFBD>й<EFBFBD>л<EFBFBD>лИИИИ
ЈшЈшЈшЈшЈшЈш
ЈшЈшЈшЈшЙ§Иј
ИјИјИјИјЙ§Pј
ИјИјИјИјИИHИ
H(H(H(H(H(H(
H(H(H(H(ИЙИИ
X8X8X8X8 ЩX8
X8X8X8X8ИИИИ
hhhhhh
hhhh<18>Нxp
xxxx -xp
xxxx

1
resources/dos32.nib generated Normal file

File diff suppressed because one or more lines are too long

View File

@ -89,7 +89,7 @@ func buildNotImplementedSoftSwitchR(io *ioC0Page) softSwitchR {
}
}
func buildNotImplementedSoftSwitchW(io *ioC0Page) softSwitchW {
func buildNotImplementedSoftSwitchW(_ *ioC0Page) softSwitchW {
return func(uint8) {
// Do nothing
}

View File

@ -4,7 +4,7 @@
## With the sequencer:
- How to begin
- DOS 3.3: Works
- DOS 3.2: **Unknown, 13 sector disks boot not supported**
- DOS 3.2: Works with 13 sectors ROM
- Next choices
- Bouncing Kamungas: Working
- Commando: Working

View File

@ -10,6 +10,7 @@ type Diskette interface {
PowerOff(cycle uint64)
Read(quarterTrack int, cycle uint64) uint8
Write(quarterTrack int, value uint8, cycle uint64)
Is13Sectors() bool
}
// IsDiskette returns true if the files looks like a 5 1/4 diskette
@ -19,14 +20,18 @@ func IsDiskette(data []byte) bool {
// MakeDiskette returns a Diskette by detecting the format
func MakeDiskette(data []byte, filename string, writeable bool) (Diskette, error) {
if isFileD13(data) {
return nil, errors.New("files with .d13 format not supported for 13 sectors disk, use .nib or .woz")
}
if isFileNib(data) {
var d diskette16sector
var d disketteNib
d.nib = newFileNib(data)
return &d, nil
}
if isFileDsk(data) {
var d diskette16sectorWritable
var d disketteNibWritable
d.nib = newFileDsk(data, filename)
d.nib.supportsWrite = d.nib.supportsWrite && writeable
return &d, nil

View File

@ -6,19 +6,19 @@ See:
https://github.com/TomHarte/CLK/wiki/Apple-GCR-disk-encoding
*/
type diskette16sector struct {
type disketteNib struct {
nib *fileNib
position int
}
func (d *diskette16sector) PowerOn(cycle uint64) {
func (d *disketteNib) PowerOn(cycle uint64) {
// Not used
}
func (d *diskette16sector) PowerOff(_ uint64) {
func (d *disketteNib) PowerOff(_ uint64) {
// Not used
}
func (d *diskette16sector) Read(quarterTrack int, cycle uint64) uint8 {
func (d *disketteNib) Read(quarterTrack int, cycle uint64) uint8 {
track := d.nib.track[quarterTrack/4]
value := track[d.position]
d.position = (d.position + 1) % nibBytesPerTrack
@ -26,8 +26,13 @@ func (d *diskette16sector) Read(quarterTrack int, cycle uint64) uint8 {
return value
}
func (d *diskette16sector) Write(quarterTrack int, value uint8, _ uint64) {
func (d *disketteNib) Write(quarterTrack int, value uint8, _ uint64) {
track := quarterTrack / 4
d.nib.track[track][d.position] = value
d.position = (d.position + 1) % nibBytesPerTrack
}
func (d *disketteNib) Is13Sectors() bool {
// It amy be 13 sectors but we don't know
return false
}

View File

@ -1,18 +1,18 @@
package storage
type diskette16sectorTimed struct {
type disketteNibTimed struct {
nib *fileNib
cycleOn uint64 // Cycle when the disk was last turned on
}
func (d *diskette16sectorTimed) PowerOn(cycle uint64) {
func (d *disketteNibTimed) PowerOn(cycle uint64) {
d.cycleOn = cycle
}
func (d *diskette16sectorTimed) PowerOff(_ uint64) {
func (d *disketteNibTimed) PowerOff(_ uint64) {
// Not needed
}
func (d *diskette16sectorTimed) getBitPositionInTrack(cycle uint64) int {
func (d *disketteNibTimed) getBitPositionInTrack(cycle uint64) int {
// Calculate how long the disk has been spinning. We move one bit every 4 cycles.
// In this implementation we don't take into account how long the motor takes to be at full speed.
cycles := cycle - d.cycleOn
@ -20,7 +20,7 @@ func (d *diskette16sectorTimed) getBitPositionInTrack(cycle uint64) int {
return int(position % (8 * nibBytesPerTrack)) // Ignore full turns
}
func (d *diskette16sectorTimed) Read(quarterTrack int, cycle uint64) uint8 {
func (d *disketteNibTimed) Read(quarterTrack int, cycle uint64) uint8 {
track := d.nib.track[quarterTrack/4]
bitPosition := d.getBitPositionInTrack(cycle)
bytePosition := bitPosition / 8
@ -35,6 +35,11 @@ func (d *diskette16sectorTimed) Read(quarterTrack int, cycle uint64) uint8 {
return value
}
func (d *diskette16sectorTimed) Write(quarterTrack int, value uint8, _ uint64) {
func (d *disketteNibTimed) Write(quarterTrack int, value uint8, _ uint64) {
panic("Write not implemented on time based disk implementation")
}
func (d *disketteNibTimed) Is13Sectors() bool {
// It amy be 13 sectors but we don't know
return false
}

View File

@ -6,7 +6,7 @@ See:
https://github.com/TomHarte/CLK/wiki/Apple-GCR-disk-encoding
*/
type diskette16sectorWritable struct {
type disketteNibWritable struct {
nib *fileNib
position int
@ -15,21 +15,21 @@ type diskette16sectorWritable struct {
dirtyTrack int
}
func (d *diskette16sectorWritable) PowerOn(cycle uint64) {
func (d *disketteNibWritable) PowerOn(cycle uint64) {
// Not used
}
func (d *diskette16sectorWritable) PowerOff(_ uint64) {
func (d *disketteNibWritable) PowerOff(_ uint64) {
d.commit()
}
func (d *diskette16sectorWritable) Read(quarterTrack int, cycle uint64) uint8 {
func (d *disketteNibWritable) Read(quarterTrack int, cycle uint64) uint8 {
track := d.nib.track[quarterTrack/4]
value := track[d.position]
d.position = (d.position + 1) % nibBytesPerTrack
return value
}
func (d *diskette16sectorWritable) Write(quarterTrack int, value uint8, _ uint64) {
func (d *disketteNibWritable) Write(quarterTrack int, value uint8, _ uint64) {
track := quarterTrack / 4
if d.hasDirtyTrack && track != d.dirtyTrack {
@ -43,9 +43,14 @@ func (d *diskette16sectorWritable) Write(quarterTrack int, value uint8, _ uint64
d.dirtyTrack = track
}
func (d *diskette16sectorWritable) commit() {
func (d *disketteNibWritable) commit() {
if d.hasDirtyTrack {
d.nib.saveTrack(d.dirtyTrack)
d.hasDirtyTrack = false
}
}
func (d *disketteNibWritable) Is13Sectors() bool {
// It amy be 13 sectors but we don't know
return false
}

View File

@ -31,9 +31,6 @@ func newDisquetteWoz(f *FileWoz) (*disketteWoz, error) {
if f.Info.DiskType != 1 {
return nil, errors.New("Only 5.25 disks are supported")
}
if f.Info.BootSectorFormat == 2 { // Info not available in WOZ 1.0
return nil, errors.New("Woz 13 sector disks are not supported")
}
var d disketteWoz
d.data = f
@ -98,3 +95,7 @@ func (d *disketteWoz) Read(quarterTrack int, cycle uint64) uint8 {
func (d *disketteWoz) Write(quarterTrack int, value uint8, _ uint64) {
panic("Write not implemented on woz disk implementation")
}
func (d *disketteWoz) Is13Sectors() bool {
return d.data.version == 2 && d.data.Info.BootSectorFormat == 2
}

View File

@ -21,6 +21,7 @@ const (
nibBytesPerTrack = 6656
nibImageSize = numberOfTracks * nibBytesPerTrack
dskImageSize = numberOfTracks * numberOfSectors * bytesPerSector
d13ImageSize = numberOfTracks * 13 * bytesPerSector
defaultVolumeTag = 254
cyclesPerBit = 4
)
@ -52,6 +53,10 @@ func isFileDsk(data []uint8) bool {
return len(data) == dskImageSize
}
func isFileD13(data []uint8) bool {
return len(data) == d13ImageSize
}
func newFileDsk(data []uint8, filename string) *fileNib {
var f fileNib