Some traces for CP/M 65, test for CP/M 65 boot and some other changes

This commit is contained in:
Iván Izaguirre 2024-01-25 22:20:22 +01:00
parent 624374f344
commit 0c615fc96c
10 changed files with 188 additions and 25 deletions

View File

@ -22,9 +22,7 @@ func testA2AuditInternal(t *testing.T, model string, removeLangCard bool, cycles
if err != nil {
t.Fatal(err)
}
at.terminateCondition = func(a *Apple2) bool {
return a.cpu.GetCycles() > cycles
}
at.terminateCondition = buildTerminateConditionTexts(at, messages, false, cycles)
at.run()
text := at.getText()

View File

@ -37,7 +37,7 @@ func (a *Apple2) Start(paused bool) {
for i := 0; i < cpuSpinLoops; i++ {
// Conditional tracing
//pc, _ := a.cpu.GetPCAndSP()
//a.cpu.SetTrace((pc >= 0xc500 && pc < 0xc600) || (pc >= 0xc700 && pc < 0xc800))
//a.cpu.SetTrace(pc >= 0x7f0 && pc < 0x0820)
// Execution
a.cpu.ExecuteInstruction()

View File

@ -1,12 +1,16 @@
package izapple2
import (
"strings"
"github.com/ivanizag/izapple2/screen"
)
type terminateConditionFunc func(a *Apple2) bool
type apple2Tester struct {
a *Apple2
terminateCondition func(a *Apple2) bool
terminateCondition terminateConditionFunc
}
func makeApple2Tester(model string, overrides *configuration) (*apple2Tester, error) {
@ -46,3 +50,43 @@ func (at *apple2Tester) getText() string {
func (at *apple2Tester) getText80() string {
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
}
}

View File

@ -19,9 +19,7 @@ func testCardDetectedInternal(t *testing.T, model string, card string, cycles ui
if err != nil {
t.Fatal(err)
}
at.terminateCondition = func(a *Apple2) bool {
return a.cpu.GetCycles() > cycles
}
at.terminateCondition = buildTerminateConditionText(at, banner, true, cycles)
at.run()
text := at.getText80()

View File

@ -11,9 +11,8 @@ func TestSwyftTutorial(t *testing.T) {
t.Fatal(err)
}
at.terminateCondition = func(a *Apple2) bool {
return a.cpu.GetCycles() > 10_000_000
}
at.terminateCondition = buildTerminateConditionText(at, "HOW TO USE SWYFTCARD", true, 10_000_000)
at.run()
text := at.getText80()

View File

@ -5,7 +5,7 @@ import (
"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()
if 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 {
t.Fatal(err)
}
at.terminateCondition = func(a *Apple2) bool {
return a.cpu.GetCycles() > cycles
}
at.terminateCondition = buildTerminateConditionTexts(at, []string{banner, prompt}, col80, cycles)
at.run()
text := at.getText()
var text string
if col80 {
text = at.getText80()
} else {
text = at.getText()
}
if !strings.Contains(text, banner) {
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) {
testBoots(t, "2plus", "", 200_000, "APPLE ][", "\n]")
testBoots(t, "2plus", "", 200_000, "APPLE ][", "\n]", false)
}
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) {
testBoots(t, "2enh", "", 200_000, "Apple //e", "\n]")
testBoots(t, "2enh", "", 200_000, "Apple //e", "\n]", false)
}
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) {
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)
}

View File

@ -72,10 +72,6 @@ func (m *memoryRangeROM) poke(address uint16, value uint8) {
// 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
func identifyMemory(m memoryHandler) string {
ram, ok := m.(*memoryRange)

BIN
resources/cpm65.po Normal file

Binary file not shown.

View File

@ -62,6 +62,11 @@ func buildTracerFactory() map[string]*traceBuilder {
description: "Panic on unimplemented softswitches",
connectFunc: func(a *Apple2) { a.io.setPanicNotImplemented(true) },
}
tracerFactory["cpm65"] = &traceBuilder{
name: "cpm65",
description: "Trace CPM65 BDOS calls",
executionTracer: newTraceCpm65(false),
}
return tracerFactory
}

116
traceCpm65.go Normal file
View File

@ -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
}