mirror of
https://github.com/ivanizag/izapple2.git
synced 2024-12-12 21:29:06 +00:00
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 {
|
||||
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()
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
BIN
resources/cpm65.po
Normal file
Binary file not shown.
@ -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
116
traceCpm65.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user