Some traces for CP/M 65, test for CP/M 65 boot and some other changes
This commit is contained in:
parent
624374f344
commit
0c615fc96c
|
@ -22,9 +22,7 @@ func testA2AuditInternal(t *testing.T, model string, removeLangCard bool, cycles
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
at.terminateCondition = func(a *Apple2) bool {
|
at.terminateCondition = buildTerminateConditionTexts(at, messages, false, cycles)
|
||||||
return a.cpu.GetCycles() > cycles
|
|
||||||
}
|
|
||||||
at.run()
|
at.run()
|
||||||
|
|
||||||
text := at.getText()
|
text := at.getText()
|
||||||
|
|
|
@ -37,7 +37,7 @@ func (a *Apple2) Start(paused bool) {
|
||||||
for i := 0; i < cpuSpinLoops; i++ {
|
for i := 0; i < cpuSpinLoops; i++ {
|
||||||
// Conditional tracing
|
// Conditional tracing
|
||||||
//pc, _ := a.cpu.GetPCAndSP()
|
//pc, _ := a.cpu.GetPCAndSP()
|
||||||
//a.cpu.SetTrace((pc >= 0xc500 && pc < 0xc600) || (pc >= 0xc700 && pc < 0xc800))
|
//a.cpu.SetTrace(pc >= 0x7f0 && pc < 0x0820)
|
||||||
|
|
||||||
// Execution
|
// Execution
|
||||||
a.cpu.ExecuteInstruction()
|
a.cpu.ExecuteInstruction()
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
package izapple2
|
package izapple2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/ivanizag/izapple2/screen"
|
"github.com/ivanizag/izapple2/screen"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type terminateConditionFunc func(a *Apple2) bool
|
||||||
|
|
||||||
type apple2Tester struct {
|
type apple2Tester struct {
|
||||||
a *Apple2
|
a *Apple2
|
||||||
terminateCondition func(a *Apple2) bool
|
terminateCondition terminateConditionFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeApple2Tester(model string, overrides *configuration) (*apple2Tester, error) {
|
func makeApple2Tester(model string, overrides *configuration) (*apple2Tester, error) {
|
||||||
|
@ -46,3 +50,43 @@ func (at *apple2Tester) getText() string {
|
||||||
func (at *apple2Tester) getText80() string {
|
func (at *apple2Tester) getText80() string {
|
||||||
return screen.RenderTextModeString(at.a, true, false, false, at.a.isApple2e)
|
return screen.RenderTextModeString(at.a, true, false, false, at.a.isApple2e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildTerminateConditionCycles(cycles uint64) terminateConditionFunc {
|
||||||
|
return func(a *Apple2) bool {
|
||||||
|
return a.cpu.GetCycles() > cycles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const textCheckInterval = uint64(100_000)
|
||||||
|
|
||||||
|
func buildTerminateConditionText(at *apple2Tester, needle string, col80 bool, timeoutCycles uint64) terminateConditionFunc {
|
||||||
|
needles := []string{needle}
|
||||||
|
return buildTerminateConditionTexts(at, needles, col80, timeoutCycles)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildTerminateConditionTexts(at *apple2Tester, needles []string, col80 bool, timeoutCycles uint64) terminateConditionFunc {
|
||||||
|
lastCheck := uint64(0)
|
||||||
|
found := false
|
||||||
|
return func(a *Apple2) bool {
|
||||||
|
cycles := a.cpu.GetCycles()
|
||||||
|
if cycles > timeoutCycles {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if cycles-lastCheck > textCheckInterval {
|
||||||
|
lastCheck = cycles
|
||||||
|
var text string
|
||||||
|
if col80 {
|
||||||
|
text = at.getText80()
|
||||||
|
} else {
|
||||||
|
text = at.getText()
|
||||||
|
}
|
||||||
|
for _, needle := range needles {
|
||||||
|
if !strings.Contains(text, needle) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -19,9 +19,7 @@ func testCardDetectedInternal(t *testing.T, model string, card string, cycles ui
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
at.terminateCondition = func(a *Apple2) bool {
|
at.terminateCondition = buildTerminateConditionText(at, banner, true, cycles)
|
||||||
return a.cpu.GetCycles() > cycles
|
|
||||||
}
|
|
||||||
at.run()
|
at.run()
|
||||||
|
|
||||||
text := at.getText80()
|
text := at.getText80()
|
||||||
|
|
|
@ -11,9 +11,8 @@ func TestSwyftTutorial(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
at.terminateCondition = func(a *Apple2) bool {
|
at.terminateCondition = buildTerminateConditionText(at, "HOW TO USE SWYFTCARD", true, 10_000_000)
|
||||||
return a.cpu.GetCycles() > 10_000_000
|
|
||||||
}
|
|
||||||
at.run()
|
at.run()
|
||||||
|
|
||||||
text := at.getText80()
|
text := at.getText80()
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testBoots(t *testing.T, model string, disk string, cycles uint64, banner string, prompt string) {
|
func testBoots(t *testing.T, model string, disk string, cycles uint64, banner string, prompt string, col80 bool) {
|
||||||
overrides := newConfiguration()
|
overrides := newConfiguration()
|
||||||
if disk != "" {
|
if disk != "" {
|
||||||
overrides.set(confS6, "diskii,disk1=\""+disk+"\"")
|
overrides.set(confS6, "diskii,disk1=\""+disk+"\"")
|
||||||
|
@ -17,12 +17,15 @@ func testBoots(t *testing.T, model string, disk string, cycles uint64, banner st
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
at.terminateCondition = func(a *Apple2) bool {
|
at.terminateCondition = buildTerminateConditionTexts(at, []string{banner, prompt}, col80, cycles)
|
||||||
return a.cpu.GetCycles() > cycles
|
|
||||||
}
|
|
||||||
at.run()
|
at.run()
|
||||||
|
|
||||||
text := at.getText()
|
var text string
|
||||||
|
if col80 {
|
||||||
|
text = at.getText80()
|
||||||
|
} else {
|
||||||
|
text = at.getText()
|
||||||
|
}
|
||||||
if !strings.Contains(text, banner) {
|
if !strings.Contains(text, banner) {
|
||||||
t.Errorf("Expected '%s', got '%s'", banner, text)
|
t.Errorf("Expected '%s', got '%s'", banner, text)
|
||||||
}
|
}
|
||||||
|
@ -33,21 +36,25 @@ func testBoots(t *testing.T, model string, disk string, cycles uint64, banner st
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPlusBoots(t *testing.T) {
|
func TestPlusBoots(t *testing.T) {
|
||||||
testBoots(t, "2plus", "", 200_000, "APPLE ][", "\n]")
|
testBoots(t, "2plus", "", 200_000, "APPLE ][", "\n]", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test2EBoots(t *testing.T) {
|
func Test2EBoots(t *testing.T) {
|
||||||
testBoots(t, "2e", "", 200_000, "Apple ][", "\n]")
|
testBoots(t, "2e", "", 200_000, "Apple ][", "\n]", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test2EnhancedBoots(t *testing.T) {
|
func Test2EnhancedBoots(t *testing.T) {
|
||||||
testBoots(t, "2enh", "", 200_000, "Apple //e", "\n]")
|
testBoots(t, "2enh", "", 200_000, "Apple //e", "\n]", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBase64Boots(t *testing.T) {
|
func TestBase64Boots(t *testing.T) {
|
||||||
testBoots(t, "base64a", "", 1_000_000, "BASE 64A", "\n]")
|
testBoots(t, "base64a", "", 1_000_000, "BASE 64A", "\n]", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPlusDOS33Boots(t *testing.T) {
|
func TestPlusDOS33Boots(t *testing.T) {
|
||||||
testBoots(t, "2plus", "<internal>/dos33.dsk", 100_000_000, "DOS VERSION 3.3", "\n]")
|
testBoots(t, "2plus", "<internal>/dos33.dsk", 100_000_000, "DOS VERSION 3.3", "\n]", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCPM65Boots(t *testing.T) {
|
||||||
|
testBoots(t, "2enh", "<internal>/cpm65.po", 5_000_000, "CP/M-65 for the Apple II", "\nA>", true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,10 +72,6 @@ func (m *memoryRangeROM) poke(address uint16, value uint8) {
|
||||||
// Ignore
|
// Ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *memoryRangeROM) subRange(a, b uint16) []uint8 {
|
|
||||||
return m.data[a-m.base+m.pageOffset : b-m.base+m.pageOffset]
|
|
||||||
}
|
|
||||||
|
|
||||||
//lint:ignore U1000 this is used to write debug code
|
//lint:ignore U1000 this is used to write debug code
|
||||||
func identifyMemory(m memoryHandler) string {
|
func identifyMemory(m memoryHandler) string {
|
||||||
ram, ok := m.(*memoryRange)
|
ram, ok := m.(*memoryRange)
|
||||||
|
|
Binary file not shown.
|
@ -62,6 +62,11 @@ func buildTracerFactory() map[string]*traceBuilder {
|
||||||
description: "Panic on unimplemented softswitches",
|
description: "Panic on unimplemented softswitches",
|
||||||
connectFunc: func(a *Apple2) { a.io.setPanicNotImplemented(true) },
|
connectFunc: func(a *Apple2) { a.io.setPanicNotImplemented(true) },
|
||||||
}
|
}
|
||||||
|
tracerFactory["cpm65"] = &traceBuilder{
|
||||||
|
name: "cpm65",
|
||||||
|
description: "Trace CPM65 BDOS calls",
|
||||||
|
executionTracer: newTraceCpm65(false),
|
||||||
|
}
|
||||||
return tracerFactory
|
return tracerFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
package izapple2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
See:
|
||||||
|
https://github.com/davidgiven/cpm65
|
||||||
|
*/
|
||||||
|
|
||||||
|
type traceCpm65 struct {
|
||||||
|
a *Apple2
|
||||||
|
skipConsole bool
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
cpm65BdosEntrypoint uint16 = 0x0804 // start-3, not really sure about this
|
||||||
|
)
|
||||||
|
|
||||||
|
func newTraceCpm65(skipConsole bool) *traceCpm65 {
|
||||||
|
var t traceCpm65
|
||||||
|
t.skipConsole = skipConsole
|
||||||
|
return &t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *traceCpm65) connect(a *Apple2) {
|
||||||
|
t.a = a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *traceCpm65) inspect() {
|
||||||
|
pc, _ := t.a.cpu.GetPCAndSP()
|
||||||
|
if pc == cpm65BdosEntrypoint {
|
||||||
|
regA, regX, regY, _ := t.a.cpu.GetAXYP()
|
||||||
|
param := uint16(regX)<<8 | uint16(regA)
|
||||||
|
switch regY {
|
||||||
|
case 2: // CONSOLE_OUTPUT
|
||||||
|
if !t.skipConsole {
|
||||||
|
fmt.Printf("CPM65 BDOS call $%02x:%s from $%04x with \"%c\"\n", regY, bdosCodeToName(regY), pc, regA)
|
||||||
|
}
|
||||||
|
case 9: // WRITE_STRING
|
||||||
|
if !t.skipConsole {
|
||||||
|
text := t.getCpmString(param)
|
||||||
|
fmt.Printf("CPM65 BDOS call $%02x:%s from $%04x with \"%s\"\n", regY, bdosCodeToName(regY), pc, text)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fmt.Printf("CPM65 BDOS call $%02x:%s from $%04x\n", regY, bdosCodeToName(regY), pc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cpm65BdosNames = []string{
|
||||||
|
"EXIT_PROGRAM", // 0
|
||||||
|
"CONSOLE_INPUT", // 1
|
||||||
|
"CONSOLE_OUTPUT", // 2
|
||||||
|
"AUX_INPUT", // 3
|
||||||
|
"AUX_OUTPUT", // 4
|
||||||
|
"PRINTER_OUTPUT", // 5
|
||||||
|
"DIRECT_IO", // 6
|
||||||
|
"GET_IO_BYTE", // 7
|
||||||
|
"SET_IO_BYTE", // 8
|
||||||
|
"WRITE_STRING", // 9
|
||||||
|
"READ_LINE", // 10
|
||||||
|
"CONSOLE_STATUS", // 11
|
||||||
|
"GET_VERSION", // 12
|
||||||
|
"RESET_DISKS", // 13
|
||||||
|
"SELECT_DISK", // 14
|
||||||
|
"OPEN_FILE", // 15
|
||||||
|
"CLOSE_FILE", // 16
|
||||||
|
"FIND_FIRST", // 17
|
||||||
|
"FIND_NEXT", // 18
|
||||||
|
"DELETE_FILE", // 19
|
||||||
|
"READ_SEQUENTIAL", // 20
|
||||||
|
"WRITE_SEQUENTIAL", // 21
|
||||||
|
"CREATE_FILE", // 22
|
||||||
|
"RENAME_FILE", // 23
|
||||||
|
"GET_LOGIN_BITMAP", // 24
|
||||||
|
"GET_CURRENT_DRIVE", // 25
|
||||||
|
"SET_DMA_ADDRESS", // 26
|
||||||
|
"GET_ALLOCATION_BITMAP", // 27
|
||||||
|
"SET_DRIVE_READONLY", // 28
|
||||||
|
"GET_READONLY_BITMAP", // 29
|
||||||
|
"SET_FILE_ATTRIBUTES", // 30
|
||||||
|
"GET_DPB", // 31
|
||||||
|
"GET_SET_USER_NUMBER", // 32
|
||||||
|
"READ_RANDOM", // 33
|
||||||
|
"WRITE_RANDOM", // 34
|
||||||
|
"COMPUTE_FILE_SIZE", // 35
|
||||||
|
"COMPUTE_RANDOM_POINTER", // 36
|
||||||
|
"RESET_DISK", // 37
|
||||||
|
"GET_BIOS", // 38
|
||||||
|
"", // 39
|
||||||
|
"WRITE_RANDOM_FILLED", // 40
|
||||||
|
"GETZP", // 41
|
||||||
|
"GETTPA", // 42
|
||||||
|
}
|
||||||
|
|
||||||
|
func bdosCodeToName(code uint8) string {
|
||||||
|
if code < uint8(len(cpm65BdosNames)) {
|
||||||
|
return cpm65BdosNames[code]
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("BDOS_%d", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *traceCpm65) getCpmString(address uint16) string {
|
||||||
|
s := ""
|
||||||
|
for {
|
||||||
|
ch := t.a.mmu.Peek(address)
|
||||||
|
if ch == '$' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
s += string(ch)
|
||||||
|
address++
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
Loading…
Reference in New Issue