diff --git a/README.md b/README.md index 840e204..5c5f665 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Portable emulator of an Apple II+ or //e. Written in Go. - MultiROM card - Dan ][ Controller card - ProDOS ROM card + - Microsoft Z80 Softcard using the [Z80](https://github.com/koron-go/z80) emulation from Koron - Useful cards not emulating a real card - Bootable SmartPort / ProDOS card with the following smartport devices: - Block device (hard disks) @@ -230,6 +231,7 @@ The available pre-configured models are: 2plus: Apple ][+ base64a: Base 64A basis108: Basis 108 + cpm: Apple ][+ with CP/M dos32: Apple ][ with 13 sectors disk adapter and DOS 3.2x swyft: swyft @@ -256,6 +258,7 @@ The available cards are: thunderclock: Clock card videx: Videx compatible 80 columns card vidhd: Firmware signature of the VidHD card to trick Total Replay to use the SHR mode + z80softcard: Microsoft Z80 SoftCard to run CP/M The available tracers are: cpm65: Trace CPM65 BDOS calls diff --git a/cardBuilder.go b/cardBuilder.go index d97ac79..f6b4abb 100644 --- a/cardBuilder.go +++ b/cardBuilder.go @@ -61,6 +61,7 @@ func getCardFactory() map[string]*cardBuilder { cardFactory["thunderclock"] = newCardThunderClockPlusBuilder() cardFactory["videx"] = newCardVidexBuilder() cardFactory["vidhd"] = newCardVidHDBuilder() + cardFactory["z80softcard"] = newCardZ80SoftCardBuilder() return cardFactory } diff --git a/cardZ80Softcard.go b/cardZ80Softcard.go new file mode 100644 index 0000000..e5ddcf3 --- /dev/null +++ b/cardZ80Softcard.go @@ -0,0 +1,117 @@ +package izapple2 + +import ( + "fmt" + + "github.com/koron-go/z80" +) + +/* + +Microsoft Z80 SoftCard +See: + http://mirrors.apple2.org.za/Apple%20II%20Documentation%20Project/Interface%20Cards/Z80%20Cards/Microsoft%20SoftCard/ + + +This card activates DMA to take control of the system. DMA is actiavted or +deactivated by writing to the Csxx area. + +The emulation works on the Apple II+, but doesn't work when 80 columns are +available. It is not working then on the Apple IIe or the Apple II+ with a +Videx card. + + +*/ + +// CardVidHD represents a VidHD card +type CardZ80SoftCard struct { + cardBase + + cpu *z80.CPU + z80Active bool +} + +func newCardZ80SoftCardBuilder() *cardBuilder { + return &cardBuilder{ + name: "Microsoft Z80 SoftCard", + description: "Microsoft Z80 SoftCard to run CP/M", + buildFunc: func(params map[string]string) (Card, error) { + var c CardZ80SoftCard + c.romCxxx = &cardROMWriteTrap{ + callback: func() { + c.flipDMA() + }, + } + + return &c, nil + }, + } +} + +func (c *CardZ80SoftCard) assign(a *Apple2, slot int) { + mem := &cardZ80SoftCardMMU{ + mmu: a.mmu, + } + + c.cpu = &z80.CPU{ + Memory: mem, + } + + c.cardBase.assign(a, slot) +} + +func (c *CardZ80SoftCard) flipDMA() { + c.tracef("Z80 DMA flip\n") + c.z80Active = !c.z80Active + if c.z80Active { + c.activateDMA() + } else { + c.deactivateDMA() + } +} + +func (c *CardZ80SoftCard) runDMACycle() { + if c.a.cpuTrace { + fmt.Printf("Z80 pc=$%04x ($%04x for the 6502) Opcode: $%02x \n", + c.cpu.PC, z80AddressTranslation(c.cpu.PC), c.cpu.Memory.Get(c.cpu.PC)) + } + c.cpu.Step() +} + +type cardROMWriteTrap struct { + callback func() +} + +func (r *cardROMWriteTrap) peek(address uint16) uint8 { + return 0 +} + +func (r *cardROMWriteTrap) poke(address uint16, value uint8) { + if address >= 0xC000 && address < 0xC800 { + r.callback() + } +} + +type cardZ80SoftCardMMU struct { + mmu *memoryManager +} + +func (m *cardZ80SoftCardMMU) Get(addr uint16) uint8 { + return m.mmu.Peek(z80AddressTranslation(addr)) +} + +func (m *cardZ80SoftCardMMU) Set(addr uint16, value uint8) { + m.mmu.Poke(z80AddressTranslation(addr), value) +} + +func z80AddressTranslation(addr uint16) uint16 { + if addr < 0xb000 { + return addr + 0x1000 + } else if addr < 0xe000 { + return addr + 0x2000 + } else if addr < 0xf000 { + return addr - 0x2000 + } else { + return addr - 0xf000 + } +} diff --git a/cardZ80softcard_test.go b/cardZ80softcard_test.go new file mode 100644 index 0000000..3039d1f --- /dev/null +++ b/cardZ80softcard_test.go @@ -0,0 +1,28 @@ +package izapple2 + +import ( + "strings" + "testing" +) + +func TestCPMBoot(t *testing.T) { + at, err := makeApple2Tester("cpm", nil) + if err != nil { + t.Fatal(err) + } + + banner := "APPLE ][ CP/M" + prompt := "A>" + at.terminateCondition = buildTerminateConditionTexts([]string{banner, prompt}, testTextMode40, 10_000_000) + + at.run() + + text := at.getText(testTextMode40) + if !strings.Contains(text, banner) { + t.Errorf("Expected '%s', got '%s'", banner, text) + } + if !strings.Contains(text, prompt) { + t.Errorf("Expected prompt '%s', got '%s'", prompt, text) + } + +} diff --git a/configs/cpm.cfg b/configs/cpm.cfg new file mode 100644 index 0000000..8bcf128 --- /dev/null +++ b/configs/cpm.cfg @@ -0,0 +1,6 @@ +name: Apple ][+ with CP/M +parent: 2plus +# Fails with 80 columns +s3: empty +s4: z80softcard +s6: diskii,disk1=/cpm_2.20B_56K.po diff --git a/doc/usage.txt b/doc/usage.txt index 2b79046..f6e97fd 100644 --- a/doc/usage.txt +++ b/doc/usage.txt @@ -51,6 +51,7 @@ The available pre-configured models are: 2plus: Apple ][+ base64a: Base 64A basis108: Basis 108 + cpm: Apple ][+ with CP/M dos32: Apple ][ with 13 sectors disk adapter and DOS 3.2x swyft: swyft @@ -77,6 +78,7 @@ The available cards are: thunderclock: Clock card videx: Videx compatible 80 columns card vidhd: Firmware signature of the VidHD card to trick Total Replay to use the SHR mode + z80softcard: Microsoft Z80 SoftCard to run CP/M The available tracers are: cpm65: Trace CPM65 BDOS calls diff --git a/go.mod b/go.mod index 2398514..28b8c99 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( fyne.io/fyne/v2 v2.1.4 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240118000515-a250818d05e3 github.com/ivanizag/iz6502 v1.4.0 + github.com/koron-go/z80 v0.10.1 github.com/pkg/profile v1.7.0 github.com/veandco/go-sdl2 v0.4.38 golang.org/x/exp v0.0.0-20240119083558-1b970713d09a diff --git a/go.sum b/go.sum index 66a0e22..8592749 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,8 @@ github.com/ivanizag/iz6502 v1.4.0 h1:7eYygUkCPwFRH0tf2JSg1k+Sy27wwPi3ActuVNVv1Uc github.com/ivanizag/iz6502 v1.4.0/go.mod h1:h4gbw3IK6WCYawi00kBhQ4ACeQkGWgqbUeAgDaQpy6s= github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc= github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE= +github.com/koron-go/z80 v0.10.1 h1:Jfb0esP/QFL4cvcr+eFECVG0Y/mA9JBLC4EKbMU5zAY= +github.com/koron-go/z80 v0.10.1/go.mod h1:ry+Zl9kRKelzaDG9UzEtUpUnXy0Yv/kk1YEaX958xdk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= diff --git a/resources/cpm_2.20B_56K.po b/resources/cpm_2.20B_56K.po new file mode 100644 index 0000000..eddfd74 Binary files /dev/null and b/resources/cpm_2.20B_56K.po differ