mirror of
https://github.com/ivanizag/izapple2.git
synced 2026-10-24 12:06:34 +00:00
DOS turns the drive off at the end of each access. At the start of the next it reads the data latch eight times, comparing each read with one 14 cycles later, to tell whether the disk still turns; if it does not seem to, it waits for the motor to come up to speed, close to a second. With the Disk II card and a DSK, PO or NIB diskette that wait was paid on every access, and a DOS 3.3 boot took 36 million cycles where the real machine takes five. Two things made the disk look still: - The motor stopped as soon as the drive was turned off. The 556 timer of the controller keeps it turning for about a second, as the sequencer card already does, and the writable diskette does not move while stopped. - The diskette gives a nibble on every read, and the 48 0xff sync nibbles of the gap after a sector, where an access ends, read the same sixteen times in a row. The latch of a turning disk is never the same nibble twice: it holds the next one being shifted in, with bit 7 clear. Every other read now gives that, which programs reading nibbles skip. DOS 3.3 reaches its banner in 5.4 million cycles, and the System Master loads Integer BASIC in 4.7 million where it took 59 million, as with the WOZ image on the sequencer card. Tests pin the boot time and check that a diskette INIT formats starts the machine.
117 lines
3.3 KiB
Go
117 lines
3.3 KiB
Go
package izapple2
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
/*
|
|
A DOS 3.3 boot on an Apple II takes a few seconds, five million cycles to the
|
|
banner. DOS turns the drive off after each access and, at the start of the
|
|
next, reads the data latch to tell whether the disk still turns, before it
|
|
waits for the motor to come up to speed. If it can't tell, each sector costs
|
|
that wait, and the boot takes more than thirty million cycles.
|
|
*/
|
|
const dos33BootCycles = 10_000_000
|
|
|
|
// cyclesToText runs a machine until a text is on the screen and returns the
|
|
// cycles it took, or fails after a limit
|
|
func cyclesToText(t *testing.T, a *Apple2, text string, limit uint64) uint64 {
|
|
t.Helper()
|
|
for !strings.Contains(a.ScreenText(), text) {
|
|
if a.GetCycles() > limit {
|
|
t.Fatalf("%q was not on the screen after %v cycles:\n%v", text, limit, a.ScreenText())
|
|
}
|
|
a.RunCycles(100_000)
|
|
}
|
|
return a.GetCycles()
|
|
}
|
|
|
|
// writableCopy is a copy of an embedded disk image in a file of the test
|
|
func writableCopy(t *testing.T, resource string) string {
|
|
t.Helper()
|
|
data, _, err := LoadResource(resource)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
path := filepath.Join(t.TempDir(), filepath.Base(resource))
|
|
if err := os.WriteFile(path, data, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return path
|
|
}
|
|
|
|
func TestDisk2BootsDOS33InFewSeconds(t *testing.T) {
|
|
for name, disk := range map[string]string{
|
|
"read only": "<internal>/dos33.dsk",
|
|
"writable": writableCopy(t, "<internal>/dos33.dsk"),
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
a, err := CreateAppleFromModel("2plus", map[string]string{"s6": "diskii,disk1=\"" + disk + "\""}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
a.Init()
|
|
cycles := cyclesToText(t, a, "DOS VERSION 3.3", 100_000_000)
|
|
t.Logf("DOS 3.3 started in %v cycles", cycles)
|
|
if cycles > dos33BootCycles {
|
|
t.Errorf("DOS 3.3 took %v cycles to start, more than %v", cycles, dos33BootCycles)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// strobedKeys is a keyboard that hands a key only once the program has
|
|
// read the one before, as the latch of the keyboard does
|
|
type strobedKeys struct {
|
|
pending []uint8
|
|
}
|
|
|
|
func (k *strobedKeys) GetKey(strobed bool) (uint8, bool) {
|
|
if !strobed || len(k.pending) == 0 {
|
|
return 0, false
|
|
}
|
|
key := k.pending[0]
|
|
k.pending = k.pending[1:]
|
|
return key, true
|
|
}
|
|
|
|
func TestDisk2InitializesADisketteThatStarts(t *testing.T) {
|
|
blank := filepath.Join(t.TempDir(), "blank.dsk")
|
|
if err := os.WriteFile(blank, make([]byte, 143_360), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
a, err := CreateAppleFromModel("2plus", map[string]string{
|
|
"s6": "diskii,disk1=<internal>/dos33.dsk,disk2=\"" + blank + "\"",
|
|
}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
keys := &strobedKeys{}
|
|
a.SetKeyboardProvider(keys)
|
|
a.Init()
|
|
cyclesToText(t, a, "DOS VERSION 3.3", 100_000_000)
|
|
|
|
for _, line := range []string{"NEW", `10 PRINT "INITIALIZED HERE"`, "INIT HELLO,D2"} {
|
|
keys.pending = append(keys.pending, []uint8(line+"\r")...)
|
|
}
|
|
start := a.GetCycles()
|
|
for len(keys.pending) > 0 {
|
|
if a.GetCycles()-start > 100_000_000 {
|
|
t.Fatalf("the keys were not read:\n%v", a.ScreenText())
|
|
}
|
|
a.RunCycles(100_000)
|
|
}
|
|
a.RunCycles(100_000_000) // INIT formats the whole diskette
|
|
|
|
again, err := CreateAppleFromModel("2plus", map[string]string{"s6": "diskii,disk1=\"" + blank + "\""}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
again.Init()
|
|
cyclesToText(t, again, "INITIALIZED HERE", 100_000_000)
|
|
}
|