Configuration redone

This commit is contained in:
Iván Izaguirre 2024-01-06 21:48:23 +01:00
parent ba4c7437b4
commit 0a1a619586
56 changed files with 1698 additions and 1264 deletions

11
.gitignore vendored
View File

@ -2,15 +2,16 @@
SDL2.dll
a2sdl.exe
frontend/a2sdl/a2sdl
frontend/a2sdl/*.woz
frontend/a2sdl/*.dsk
frontend/a2sdl/*.po
frontend/a2sdl/*.2mg
frontend/*/*.woz
frontend/*/*.dsk
frontend/*/*.po
frontend/*/*.2mg
frontend/*/*.hdv
frontend/a2fyne/a2fyne
frontend/headless/headless
frontend/*/snapshot.gif
frontend/*/snapshot.png
frontend/*/printer.out
printer.out
.DS_STORE
.scannerwork
coverage.out

120
README.md
View File

@ -186,93 +186,53 @@ Only valid on SDL mode
### Command line options
```terminal
-brainBoardSlot int
slot for the Brain Board II. -1 for none (default -1)
-charRom string
rom file for the character generator (default "<default>")
-consoleCardSlot int
slot for the host console card. -1 for none (default -1)
-disk string
file to load on the first disk drive (default "<internal>/dos33.dsk")
-disk2Slot int
slot for the disk driver. -1 for none. (default 6)
-disk35 string
file to load on the SmartPort disk (slot 5)
-diskRom string
rom file for the disk drive controller (default "<internal>/DISK2.rom")
-diskb string
file to load on the second disk drive
-diskc string
file to load on the third disk drive, slot 5
-diskd string
file to load on the fourth disk drive, slot 5
-fastChipSlot int
slot for the FASTChip accelerator card, -1 for none (default 3)
Usage: izapple [file]
file
path to image to use on the boot device
-charrom string
rom file for the character generator (default "<internal>/Apple IIe Video Enhanced.bin")
-cpu string
cpu type, can be '6502' or '65c02' (default "65c02")
-forceCaps
force all letters to be uppercased (no need for caps lock!)
-fastDisk
set fast mode when the disks are spinning (default true)
-hd string
file to load on the boot hard disk
-hdSlot int
slot for the hard drive if present. -1 for none. (default -1)
-languageCardSlot int
slot for the 16kb language card. -1 for none
-memoryExpSlot int
slot for the Memory Expansion card with 1GB. -1 for none (default -1)
-mhz float
cpu speed in Mhz, use 0 for full speed. Use F5 to toggle. (default 1.0227142857142857)
force all letters to be uppercased (no need for caps lock!)
-model string
set base model. Models available 2plus, 2e, 2enh, base64a (default "2enh")
-mouseCardSlot int
slot for the Mouse card. -1 for none (default 4)
-nsc int
add a DS1216 No-Slot-Clock on the main ROM (use 0) or a slot ROM. -1 for none (default -1)
-panicSS
panic if a not implemented softswitch is used
-printer int
slot for the Parallel Printer Interface. -1 for none (default 1)
set base model (default "2enh")
-nsc string
add a DS1216 No-Slot-Clock on the main ROM (use 'main') or a slot ROM (default "none")
-profile
generate profile trace to analyse with pprof
-ramworks int
memory to use with RAMWorks card, 0 for no card, max is 16384 (default 8192)
generate profile trace to analyse with pprof
-ramworks string
memory to use with RAMWorks card, max is 16384 (default "none")
-rgb
emulate the RGB modes of the 80col RGB card for DHGR (default true)
emulate the RGB modes of the 80col RGB card for DHGR
-rom string
main rom file (default "<default>")
main rom file (default "<internal>/Apple2e_Enhanced.rom")
-romx
emulate a RomX
-saturnCardSlot int
slot for the 256kb Saturn card. -1 for none (default -1)
-sequencer
use the sequencer based Disk II card
-swyftCard
activate a Swyft Card in slot 3. Load the tutorial disk if none provided
-thunderClockCardSlot int
slot for the ThunderClock Plus card. -1 for none (default 4)
-traceBBC
trace BBC MOS API calls used with Applecorn, skip console I/O calls
-traceBBCFull
trace BBC MOS API calls used with Applecorn
-traceCpu
dump to the console the CPU execution. Use F11 to toggle.
-traceHD
dump to the console the hd/smartPort commands
-traceMLI
dump to the console the calls to ProDOS machine language interface calls to $BF00
-tracePascal
dump to the console the calls to the Apple Pascal BIOS
-traceSS
dump to the console the sofswitches calls
-traceSSReg
dump to the console the sofswitch registrations
-traceTracks
dump to the console the disk tracks changes
-vidHDSlot int
slot for the VidHD card, only for //e models. -1 for none (default 2)
-videxCardSlot int
slot for the Videx Videoterm 80 columns card. For pre-2e models. -1 for none (default 3)
emulate a RomX
-s0 string
slot 0 configuration. (default "language")
-s1 string
slot 1 configuration. (default "parallel")
-s2 string
slot 2 configuration. (default "vidhd")
-s3 string
slot 3 configuration. (default "fastchip")
-s4 string
slot 4 configuration. (default "mouse")
-s5 string
slot 5 configuration. (default "empty")
-s6 string
slot 6 configuration. (default "diskii,disk1=<internal>/dos33.dsk")
-s7 string
slot 7 configuration. (default "empty")
-speed string
cpu speed in Mhz, can be 'ntsc', 'pal', 'full' or a decimal nunmber (default "ntsc")
-trace string
trace CPU execution with one or more comma separated tracers (default "none")
The available pre configured models are: swyft, 2e, 2enh, 2plus, base64a.
The available cards are: brainboard, diskii, memexp, mouse, swyftcard, inout, smartport, thunderclock, fujinet, videx, vidhd, diskiiseq, fastchip, language, softswitchlogger, parallel, saturn.
The available tracers are: ucsd, cpu, ss, ssreg, panicSS, mos, mosfull, mli.
```

203
apple2.go
View File

@ -1,26 +1,27 @@
package izapple2
import (
"fmt"
"sync/atomic"
"time"
"github.com/ivanizag/iz6502"
)
// Apple2 represents all the components and state of the emulated machine
type Apple2 struct {
Name string
cpu *iz6502.State
mmu *memoryManager
io *ioC0Page
cg *CharacterGenerator
cards [8]Card
softVideoSwitch *SoftVideoSwitch
isApple2e bool
commandChannel chan command
Name string
cpu *iz6502.State
mmu *memoryManager
io *ioC0Page
cg *CharacterGenerator
cards [8]Card
tracers []executionTracer
softVideoSwitch *SoftVideoSwitch
board string
isApple2e bool
commandChannel chan command
cycleDurationNs float64 // Current speed. Inverse of the cpu clock in Ghz
fastMode bool
fastRequestsCounter int32
cycleBreakpoint uint64
breakPoint bool
@ -28,131 +29,32 @@ type Apple2 struct {
showSpeed bool
paused bool
forceCaps bool
tracers []executionTracer
removableMediaDrives []drive
}
type executionTracer interface {
inspect()
// GetCards returns the array of inserted cards
func (a *Apple2) GetCards() [8]Card {
return a.cards
}
const (
// CPUClockMhz is the actual Apple II clock speed
CPUClockMhz = 14.318 / 14
cpuClockEuroMhz = 14.238 / 14
)
const (
maxWaitDuration = 100 * time.Millisecond
cpuSpinLoops = 100
)
// Run starts the Apple2 emulation
func (a *Apple2) Run() {
a.Start(false)
// SetKeyboardProvider attaches an external keyboard provider
func (a *Apple2) SetKeyboardProvider(kb KeyboardProvider) {
a.io.setKeyboardProvider(kb)
}
// Start the Apple2 emulation, can start paused
func (a *Apple2) Start(paused bool) {
// Start the processor
a.cpu.Reset()
referenceTime := time.Now()
speedReferenceTime := referenceTime
speedReferenceCycles := uint64(0)
a.paused = paused
for {
// Run 6502 steps
if !a.paused {
for i := 0; i < cpuSpinLoops; i++ {
// Conditional tracing
//pc, _ := a.cpu.GetPCAndSP()
//a.cpu.SetTrace((pc >= 0xc500 && pc < 0xc600) || (pc >= 0xc700 && pc < 0xc800))
// Execution
a.cpu.ExecuteInstruction()
// Special tracing
a.executionTrace()
}
if a.cycleBreakpoint != 0 && a.cpu.GetCycles() >= a.cycleBreakpoint {
a.breakPoint = true
a.cycleBreakpoint = 0
a.paused = true
}
} else {
time.Sleep(200 * time.Millisecond)
}
// Execute meta commands
commandsPending := true
for commandsPending {
select {
case command := <-a.commandChannel:
switch command.getId() {
case CommandKill:
return
case CommandPause:
if !a.paused {
a.paused = true
}
case CommandStart:
if a.paused {
a.paused = false
referenceTime = time.Now()
speedReferenceTime = referenceTime
}
case CommandPauseUnpause:
a.paused = !a.paused
referenceTime = time.Now()
speedReferenceTime = referenceTime
default:
// Execute the other commands
a.executeCommand(command)
}
default:
commandsPending = false
}
}
if a.cycleDurationNs != 0 && a.fastRequestsCounter <= 0 {
// Wait until next 6502 step has to run
clockDuration := time.Since(referenceTime)
simulatedDuration := time.Duration(float64(a.cpu.GetCycles()) * a.cycleDurationNs)
waitDuration := simulatedDuration - clockDuration
if waitDuration > maxWaitDuration || -waitDuration > maxWaitDuration {
// We have to wait too long or are too much behind. Let's fast forward
referenceTime = referenceTime.Add(-waitDuration)
waitDuration = 0
}
if waitDuration > 0 {
time.Sleep(waitDuration)
}
}
if a.showSpeed && a.cpu.GetCycles()-speedReferenceCycles > 1000000 {
// Calculate speed in MHz every million cycles
newTime := time.Now()
newCycles := a.cpu.GetCycles()
elapsedCycles := float64(newCycles - speedReferenceCycles)
freq := 1000.0 * elapsedCycles / float64(newTime.Sub(speedReferenceTime).Nanoseconds())
fmt.Printf("Freq: %f Mhz\n", freq)
speedReferenceTime = newTime
speedReferenceCycles = newCycles
}
}
// SetSpeakerProvider attaches an external keyboard provider
func (a *Apple2) SetSpeakerProvider(s SpeakerProvider) {
a.io.setSpeakerProvider(s)
}
func (a *Apple2) reset() {
a.cpu.Reset()
a.mmu.reset()
for _, c := range a.cards {
if c != nil {
c.reset()
}
}
// SetJoysticksProvider attaches an external joysticks provider
func (a *Apple2) SetJoysticksProvider(j JoysticksProvider) {
a.io.setJoysticksProvider(j)
}
// SetMouseProvider attaches an external joysticks provider
func (a *Apple2) SetMouseProvider(m MouseProvider) {
a.io.setMouseProvider(m)
}
// IsPaused returns true when emulator is paused
@ -174,20 +76,11 @@ func (a *Apple2) BreakPoint() bool {
return a.breakPoint
}
func (a *Apple2) setProfiling(value bool) {
a.profile = value
}
// IsProfiling returns true when profiling
func (a *Apple2) IsProfiling() bool {
return a.profile
}
// SetForceCaps allows the caps state to be toggled at runtime
func (a *Apple2) SetForceCaps(value bool) {
a.forceCaps = value
}
// IsForceCaps returns true when all letters are forced to upper case
func (a *Apple2) IsForceCaps() bool {
return a.forceCaps
@ -195,41 +88,13 @@ func (a *Apple2) IsForceCaps() bool {
func (a *Apple2) RequestFastMode() {
// Note: if the fastMode is shorter than maxWaitDuration, there won't be any gain.
if a.fastMode {
atomic.AddInt32(&a.fastRequestsCounter, 1)
}
atomic.AddInt32(&a.fastRequestsCounter, 1)
}
func (a *Apple2) ReleaseFastMode() {
if a.fastMode {
atomic.AddInt32(&a.fastRequestsCounter, -1)
}
atomic.AddInt32(&a.fastRequestsCounter, -1)
}
func (a *Apple2) executionTrace() {
for _, v := range a.tracers {
v.inspect()
}
}
func (a *Apple2) dumpDebugInfo() {
// See "Apple II Monitors Peeled"
pageZeroSymbols := map[int]string{
0x36: "CSWL",
0x37: "CSWH",
0x38: "KSWL",
0x39: "KSWH",
0xe2: "ACJVAFLDL", // Apple Pascal
0xe3: "ACJVAFLDH", // Apple Pascal
0xec: "JVBFOLDL", // Apple Pascal
0xed: "JVBFOLDH", // Apple Pascal
0xee: "JVAFOLDL", // Apple Pascal
0xef: "JVAFOLDH", // Apple Pascal
}
fmt.Printf("Page zero values:\n")
for _, k := range []int{0x36, 0x37, 0x38, 0x39, 0xe2, 0xe3, 0xec, 0xed, 0xee, 0xef} {
d := a.mmu.physicalMainRAM.data[k]
fmt.Printf(" %v(0x%x): 0x%02x\n", pageZeroSymbols[k], k, d)
}
func (a *Apple2) registerRemovableMediaDrive(d drive) {
a.removableMediaDrives = append(a.removableMediaDrives, d)
}

153
apple2Run.go Normal file
View File

@ -0,0 +1,153 @@
package izapple2
import (
"fmt"
"time"
)
const (
// CPUClockMhz is the actual Apple II clock speed
CPUClockMhz = 14.318 / 14
cpuClockEuroMhz = 14.238 / 14
)
const (
maxWaitDuration = 100 * time.Millisecond
cpuSpinLoops = 100
)
// Run starts the Apple2 emulation
func (a *Apple2) Run() {
a.Start(false)
}
// Start the Apple2 emulation, can start paused
func (a *Apple2) Start(paused bool) {
// Start the processor
a.cpu.Reset()
referenceTime := time.Now()
speedReferenceTime := referenceTime
speedReferenceCycles := uint64(0)
a.paused = paused
for {
// Run 6502 steps
if !a.paused {
for i := 0; i < cpuSpinLoops; i++ {
// Conditional tracing
//pc, _ := a.cpu.GetPCAndSP()
//a.cpu.SetTrace((pc >= 0xc500 && pc < 0xc600) || (pc >= 0xc700 && pc < 0xc800))
// Execution
a.cpu.ExecuteInstruction()
// Special tracing
a.executionTrace()
}
if a.cycleBreakpoint != 0 && a.cpu.GetCycles() >= a.cycleBreakpoint {
a.breakPoint = true
a.cycleBreakpoint = 0
a.paused = true
}
} else {
time.Sleep(200 * time.Millisecond)
}
// Execute meta commands
commandsPending := true
for commandsPending {
select {
case command := <-a.commandChannel:
switch command.getId() {
case CommandKill:
return
case CommandPause:
if !a.paused {
a.paused = true
}
case CommandStart:
if a.paused {
a.paused = false
referenceTime = time.Now()
speedReferenceTime = referenceTime
}
case CommandPauseUnpause:
a.paused = !a.paused
referenceTime = time.Now()
speedReferenceTime = referenceTime
default:
// Execute the other commands
a.executeCommand(command)
}
default:
commandsPending = false
}
}
if a.cycleDurationNs != 0 && a.fastRequestsCounter <= 0 {
// Wait until next 6502 step has to run
clockDuration := time.Since(referenceTime)
simulatedDuration := time.Duration(float64(a.cpu.GetCycles()) * a.cycleDurationNs)
waitDuration := simulatedDuration - clockDuration
if waitDuration > maxWaitDuration || -waitDuration > maxWaitDuration {
// We have to wait too long or are too much behind. Let's fast forward
referenceTime = referenceTime.Add(-waitDuration)
waitDuration = 0
}
if waitDuration > 0 {
time.Sleep(waitDuration)
}
}
if a.showSpeed && a.cpu.GetCycles()-speedReferenceCycles > 1000000 {
// Calculate speed in MHz every million cycles
newTime := time.Now()
newCycles := a.cpu.GetCycles()
elapsedCycles := float64(newCycles - speedReferenceCycles)
freq := 1000.0 * elapsedCycles / float64(newTime.Sub(speedReferenceTime).Nanoseconds())
fmt.Printf("Freq: %f Mhz\n", freq)
speedReferenceTime = newTime
speedReferenceCycles = newCycles
}
}
}
func (a *Apple2) reset() {
a.cpu.Reset()
a.mmu.reset()
for _, c := range a.cards {
if c != nil {
c.reset()
}
}
}
func (a *Apple2) executionTrace() {
for _, v := range a.tracers {
v.inspect()
}
}
func (a *Apple2) dumpDebugInfo() {
// See "Apple II Monitors Peeled"
pageZeroSymbols := map[int]string{
0x36: "CSWL",
0x37: "CSWH",
0x38: "KSWL",
0x39: "KSWH",
0xe2: "ACJVAFLDL", // Apple Pascal
0xe3: "ACJVAFLDH", // Apple Pascal
0xec: "JVBFOLDL", // Apple Pascal
0xed: "JVBFOLDH", // Apple Pascal
0xee: "JVAFOLDL", // Apple Pascal
0xef: "JVAFOLDH", // Apple Pascal
}
fmt.Printf("Page zero values:\n")
for _, k := range []int{0x36, 0x37, 0x38, 0x39, 0xe2, 0xe3, 0xec, 0xed, 0xee, 0xef} {
d := a.mmu.physicalMainRAM.data[k]
fmt.Printf(" %v(0x%x): 0x%02x\n", pageZeroSymbols[k], k, d)
}
}

View File

@ -1,288 +0,0 @@
package izapple2
import (
"errors"
"github.com/ivanizag/iz6502"
)
func newApple2() *Apple2 {
var a Apple2
a.Name = "Pending"
a.mmu = newMemoryManager(&a)
a.io = newIoC0Page(&a)
return &a
}
func (a *Apple2) setup(clockMhz float64, fastMode bool) {
a.commandChannel = make(chan command, 100)
a.fastMode = fastMode
if clockMhz <= 0 {
// Full speed
a.cycleDurationNs = 0
} else {
a.cycleDurationNs = 1000.0 / clockMhz
}
}
func setApple2plus(a *Apple2) {
a.Name = "Apple ][+"
a.cpu = iz6502.NewNMOS6502(a.mmu)
addApple2SoftSwitches(a.io)
}
func setApple2e(a *Apple2) {
a.Name = "Apple IIe"
a.isApple2e = true
a.cpu = iz6502.NewNMOS6502(a.mmu)
a.mmu.initExtendedRAM(1)
addApple2SoftSwitches(a.io)
addApple2ESoftSwitches(a.io)
}
func setApple2eEnhanced(a *Apple2) {
a.Name = "Apple //e"
a.isApple2e = true
a.cpu = iz6502.NewCMOS65c02(a.mmu)
a.mmu.initExtendedRAM(1)
addApple2SoftSwitches(a.io)
addApple2ESoftSwitches(a.io)
}
func (a *Apple2) addTracer(tracer executionTracer) {
a.tracers = append(a.tracers, tracer)
}
func (a *Apple2) registerRemovableMediaDrive(d drive) {
a.removableMediaDrives = append(a.removableMediaDrives, d)
}
func (a *Apple2) insertCard(c Card, slot int) {
c.assign(a, slot)
a.cards[slot] = c
}
// GetCards returns the array of inserted cards
func (a *Apple2) GetCards() [8]Card {
return a.cards
}
const (
apple2RomSize = 12 * 1024
apple2eRomSize = 16 * 1024
)
// LoadRom loads a standard Apple2+ or 2e ROM
func (a *Apple2) LoadRom(filename string) error {
data, _, err := LoadResource(filename)
if err != nil {
return err
}
size := len(data)
if size != apple2RomSize && size != apple2eRomSize {
return errors.New("rom size not supported")
}
romBase := 0x10000 - size
a.mmu.physicalROM[0] = newMemoryRangeROM(uint16(romBase), data, "Main ROM")
return nil
}
// AddDisk2 inserts a DiskII controller
func (a *Apple2) AddDisk2(slot int, diskImage, diskBImage string, trackTracer trackTracer) error {
c := NewCardDisk2(trackTracer)
a.insertCard(c, slot)
if diskImage != "" {
err := c.drive[0].insertDiskette(diskImage)
if err != nil {
return err
}
}
if diskBImage != "" {
err := c.drive[1].insertDiskette(diskBImage)
if err != nil {
return err
}
}
return nil
}
// AddDisk2 inserts a DiskII controller
func (a *Apple2) AddDisk2Sequencer(slot int, diskImage, diskBImage string, trackTracer trackTracer) error {
c := NewCardDisk2Sequencer(trackTracer)
a.insertCard(c, slot)
if diskImage != "" {
err := c.drive[0].insertDiskette(diskImage)
if err != nil {
return err
}
}
if diskBImage != "" {
err := c.drive[1].insertDiskette(diskBImage)
if err != nil {
return err
}
}
return nil
}
// AddSmartPortDisk adds a smart port card and image
func (a *Apple2) AddSmartPortDisk(slot int, hdImage string, traceHD bool, traceSP bool) error {
c := NewCardSmartPort()
c.trace = traceSP
a.insertCard(c, slot)
err := c.LoadImage(hdImage, traceHD)
return err
}
// AddSmartPortDisk adds a smart port card and image
func (a *Apple2) AddFujinet(slot int, trace bool) {
c := NewCardSmartPort()
c.trace = trace
a.insertCard(c, slot)
net := NewSmartPortFujinetNetwork(c)
net.trace = trace
c.AddDevice(net)
clock := NewSmartPortFujinetClock(c)
clock.trace = trace
c.AddDevice(clock)
}
// AddVidHD adds a card with the signature of VidHD
func (a *Apple2) AddVidHD(slot int) {
a.insertCard(NewCardVidHD(), slot)
}
// AddFastChip adds a card with the signature of VidHD
func (a *Apple2) AddFastChip(slot int) {
a.insertCard(NewCardFastChip(), slot)
}
// AddLanguageCard inserts a 16Kb card
func (a *Apple2) AddLanguageCard(slot int) {
a.insertCard(NewCardLanguage(), slot)
}
// AddSaturnCard inserts a 128Kb card
func (a *Apple2) AddSaturnCard(slot int) {
a.insertCard(NewCardSaturn(), slot)
}
// AddParallelPrinter inserts an Apple II Parallel Printer card
func (a *Apple2) AddParallelPrinter(slot int) {
a.insertCard(NewCardParallelPrinter(), slot)
}
// AddMemoryExpansionCard inserts an Apple II Memory Expansion card with 1GB
func (a *Apple2) AddMemoryExpansionCard(slot int) {
a.insertCard(NewCardMemoryExpansion(), slot)
}
// AddThunderClockPlusCard inserts a ThunderClock Plus clock card
func (a *Apple2) AddThunderClockPlusCard(slot int) {
a.insertCard(NewCardThunderClockPlus(), slot)
}
// AddMouseCard inserts a Mouse card
func (a *Apple2) AddMouseCard(slot int) {
a.insertCard(NewCardMouse(), slot)
}
// AddVidexCard inserts a Videx card
func (a *Apple2) AddVidexCard(slot int) {
c := NewCardVidex()
a.insertCard(c, slot)
a.softVideoSwitch = NewSoftVideoSwitch(c)
}
// AddSwyftCard inserts a Swyft card in slot 3
func (a *Apple2) AddSwyftCard() {
c := NewCardSwyft()
a.insertCard(c, 3)
}
// AddBrainBoardII inserts a Brain Board II card
func (a *Apple2) AddBrainBoardII(slot int) {
a.insertCard(NewCardBrainBoardII(), slot)
}
// AddRGBCard inserts an RBG option to the Apple IIe 80 col 64KB card
func (a *Apple2) AddRGBCard() {
setupRGBCard(a)
}
// AddRAMWorks inserts adds RAMWorks style RAM to the Apple IIe 80 col 64KB card
func (a *Apple2) AddRAMWorks(banks int) {
setupRAMWorksCard(a, banks)
}
// AddNoSlotClock inserts a DS1215 no slot clock under the main ROM
func (a *Apple2) AddNoSlotClock() {
nsc := newNoSlotClockDS1216(a, a.mmu.physicalROM[0])
a.mmu.physicalROM[0] = nsc
}
// AddRomX inserts a RomX. It intercepts all memory accesses
func (a *Apple2) AddRomX() error {
rx, err := newRomX(a, a.mmu)
if err != nil {
return err
}
a.cpu.SetMemory(rx)
return nil
}
// AddNoSlotClockInCard inserts a DS1215 no slot clock under a card ROM
func (a *Apple2) AddNoSlotClockInCard(slot int) error {
cardRom := a.mmu.cardsROM[slot]
if cardRom == nil {
return errors.New("no ROM available on the slot to add a no slot clock")
}
nsc := newNoSlotClockDS1216(a, cardRom)
a.mmu.cardsROM[slot] = nsc
return nil
}
// AddCardLogger inserts a fake card that logs accesses
func (a *Apple2) AddCardLogger(slot int) {
c := NewCardLogger()
a.insertCard(c, slot)
}
// AddCardInOut inserts a fake card that interfaces with the emulator host
func (a *Apple2) AddCardInOut(slot int) {
c := NewCardInOut()
a.insertCard(c, slot)
}
// SetKeyboardProvider attaches an external keyboard provider
func (a *Apple2) SetKeyboardProvider(kb KeyboardProvider) {
a.io.setKeyboardProvider(kb)
}
// SetSpeakerProvider attaches an external keyboard provider
func (a *Apple2) SetSpeakerProvider(s SpeakerProvider) {
a.io.setSpeakerProvider(s)
}
// SetJoysticksProvider attaches an external joysticks provider
func (a *Apple2) SetJoysticksProvider(j JoysticksProvider) {
a.io.setJoysticksProvider(j)
}
// SetMouseProvider attaches an external joysticks provider
func (a *Apple2) SetMouseProvider(m MouseProvider) {
a.io.setMouseProvider(m)
}

View File

@ -9,17 +9,24 @@ type apple2Tester struct {
terminateCondition func(a *Apple2) bool
}
func makeApple2Tester(model string) *apple2Tester {
a := newApple2()
a.setup(0, true) // Full speed
initModel(a, model, defaultInternal, defaultInternal)
a.AddLanguageCard(0)
func makeApple2Tester(model string, overrides *configuration) (*apple2Tester, error) {
config, err := getConfigurationFromModel(model, overrides)
if err != nil {
return nil, err
}
config.set(confSpeed, "full")
a, err := configure(config)
if err != nil {
return nil, err
}
var at apple2Tester
at.a = a
a.addTracer(&at)
return &at
return &at, nil
}
func (at *apple2Tester) connect(a *Apple2) {
at.a = a
}
func (at *apple2Tester) inspect() {

View File

@ -1,446 +0,0 @@
package izapple2
import (
"flag"
)
const defaultInternal = "<default>"
// MainApple is a device independent main. Video, keyboard and speaker won't be defined
func MainApple() *Apple2 {
romFile := flag.String(
"rom",
defaultInternal,
"main rom file")
disk2Slot := flag.Int(
"disk2Slot",
6,
"slot for the disk driver. -1 for none.")
diskImage := flag.String(
"disk",
defaultInternal,
"file to load on the first disk drive")
diskBImage := flag.String(
"diskb",
"",
"file to load on the second disk drive")
diskCImage := flag.String(
"diskc",
"",
"file to load on the third disk drive, slot 5")
diskDImage := flag.String(
"diskd",
"",
"file to load on the fourth disk drive, slot 5")
hardDiskImage := flag.String(
"hd",
"",
"file to load on the boot hard disk (slot 7)")
hardDiskSlot := flag.Int(
"hdSlot",
-1,
"slot for the hard drive if present. -1 for none.")
fujinetSlot := flag.Int(
"fujinet",
-1,
"slot for the smatport card hosting the Fujinet. -1 for none.")
smartPortImage := flag.String(
"disk35",
"",
"file to load on the SmartPort disk (slot 5)")
cpuClock := flag.Float64(
"mhz",
CPUClockMhz,
"cpu speed in Mhz, use 0 for full speed. Use F5 to toggle.")
charRomFile := flag.String(
"charRom",
defaultInternal,
"rom file for the character generator")
languageCardSlot := flag.Int(
"languageCardSlot",
0,
"slot for the 16kb language card. -1 for none")
saturnCardSlot := flag.Int(
"saturnCardSlot",
-1,
"slot for the 256kb Saturn card. -1 for none")
vidHDCardSlot := flag.Int(
"vidHDSlot",
2,
"slot for the VidHD card, only for //e models. -1 for none")
fastChipCardSlot := flag.Int(
"fastChipSlot",
3,
"slot for the FASTChip accelerator card, -1 for none")
memoryExpansionCardSlot := flag.Int(
"memoryExpSlot",
-1,
"slot for the Memory Expansion card with 1GB. -1 for none")
parallelPrinterSlot := flag.Int(
"printer",
1,
"slot for the Parallel Printer Interface. -1 for none")
brainBoard := flag.Int(
"brainBoardSlot",
-1,
"slot for the Brain Board II. -1 for none")
ramWorksKb := flag.Int(
"ramworks",
8192,
"memory to use with RAMWorks card, 0 for no card, max is 16384")
thunderClockCardSlot := flag.Int(
"thunderClockCardSlot",
-1,
"slot for the ThunderClock Plus card. -1 for none")
consoleCardSlot := flag.Int(
"consoleCardSlot",
-1,
"slot for the host console card. -1 for none")
mouseCardSlot := flag.Int(
"mouseCardSlot",
4,
"slot for the Mouse card. -1 for none")
videxCardSlot := flag.Int(
"videxCardSlot",
3,
"slot for the Videx Videoterm 80 columns card. For pre-2e models. -1 for none")
swyftCard := flag.Bool(
"swyftCard",
false,
"activate a Swyft Card in slot 3. Load the tutorial disk if none provided")
nsc := flag.Int(
"nsc",
-1,
"add a DS1216 No-Slot-Clock on the main ROM (use 0) or a slot ROM. -1 for none")
rgbCard := flag.Bool(
"rgb",
true,
"emulate the RGB modes of the 80col RGB card for DHGR")
romX := flag.Bool(
"romx",
false,
"emulate a RomX")
fastDisk := flag.Bool(
"fastDisk",
true,
"set fast mode when the disks are spinning")
panicSS := flag.Bool(
"panicSS",
false,
"panic if a not implemented softswitch is used")
traceCPU := flag.Bool(
"traceCpu",
false,
"dump to the console the CPU execution operations")
traceSS := flag.Bool(
"traceSS",
false,
"dump to the console the sofswitches calls")
traceSSReg := flag.Bool(
"traceSSReg",
false,
"dump to the console the sofswitch registrations")
traceHD := flag.Bool(
"traceHD",
false,
"dump to the console the hd accesses")
traceSP := flag.Bool(
"traceSP",
false,
"dump to the console the smarport commands")
traceTracks := flag.Bool(
"traceTracks",
false,
"dump to the console the disk tracks changes")
model := flag.String(
"model",
"2enh",
"set base model. Models available 2plus, 2e, 2enh, base64a")
profile := flag.Bool(
"profile",
false,
"generate profile trace to analyse with pprof")
traceMLI := flag.Bool(
"traceMLI",
false,
"dump to the console the calls to ProDOS machine language interface calls to $BF00")
tracePascal := flag.Bool(
"tracePascal",
false,
"dump to the console the calls to the Apple Pascal BIOS")
forceCaps := flag.Bool(
"forceCaps",
false,
"force all letters to be uppercased (no need for caps lock!)")
sequencerDisk2 := flag.Bool(
"sequencer",
false,
"use the sequencer based Disk II card")
traceBBC := flag.Bool(
"traceBBC",
false,
"trace BBC MOS API calls used with Applecorn, skip console I/O calls")
traceBBCFull := flag.Bool(
"traceBBCFull",
false,
"trace BBC MOS API calls used with Applecorn")
flag.Parse()
// Process a filename with autodetection
filename := flag.Arg(0)
diskImageFinal := *diskImage
hardDiskImageFinal := *hardDiskImage
if filename != "" {
// Try loading as diskette
_, err := LoadDiskette(filename)
if err == nil {
diskImageFinal = filename
} else {
hardDiskImageFinal = filename
}
}
// Resolve what is the default disk to use if not specified
if diskImageFinal == defaultInternal {
if *swyftCard {
diskImageFinal = "<internal>/SwyftWare_-_SwyftCard_Tutorial.woz"
} else {
diskImageFinal = "<internal>/dos33.dsk"
}
}
a := newApple2()
a.setup(*cpuClock, *fastDisk)
a.io.setTrace(*traceSS)
a.io.setTraceRegistrations(*traceSSReg)
a.io.setPanicNotImplemented(*panicSS)
a.setProfiling(*profile)
a.SetForceCaps(*forceCaps)
if *traceMLI {
a.addTracer(newTraceProDOS(a))
}
if *tracePascal {
a.addTracer(newTracePascal(a))
}
if *traceBBC {
a.addTracer(newTraceApplecorn(a, true))
}
if *traceBBCFull {
a.addTracer(newTraceApplecorn(a, false))
}
initModel(a, *model, *romFile, *charRomFile)
a.cpu.SetTrace(*traceCPU)
// Disable incompatible cards
switch *model {
case "2plus":
*vidHDCardSlot = -1
case "2e":
*videxCardSlot = -1
case "2enh":
*videxCardSlot = -1
case "base64a":
*vidHDCardSlot = -1
*videxCardSlot = -1 // The videx firmware crashes the BASE64A, probably by use of ANN0
default:
panic("Model not supported")
}
// Externsion cards
if *languageCardSlot >= 0 {
a.AddLanguageCard(*languageCardSlot)
}
if *saturnCardSlot >= 0 {
a.AddSaturnCard(*saturnCardSlot)
}
if *parallelPrinterSlot >= 0 {
a.AddParallelPrinter(*parallelPrinterSlot)
}
if *memoryExpansionCardSlot >= 0 {
a.AddMemoryExpansionCard(*memoryExpansionCardSlot)
}
if *thunderClockCardSlot > 0 {
a.AddThunderClockPlusCard(*thunderClockCardSlot)
}
if *vidHDCardSlot >= 0 {
a.AddVidHD(*vidHDCardSlot)
}
if *consoleCardSlot >= 0 {
a.AddCardInOut(*consoleCardSlot)
}
if *mouseCardSlot > 0 {
a.AddMouseCard(*mouseCardSlot)
}
if *videxCardSlot > 0 {
a.AddVidexCard(*videxCardSlot)
}
if *swyftCard {
if !a.isApple2e {
panic("SwyftCard available only on Apple IIe or better")
}
a.AddSwyftCard()
}
if *brainBoard > 0 {
a.AddBrainBoardII(*brainBoard)
}
var trackTracer trackTracer
if *traceTracks {
trackTracer = makeTrackTracerLogger()
}
if *smartPortImage != "" {
err := a.AddSmartPortDisk(5, *smartPortImage, *traceHD, *traceSP)
if err != nil {
panic(err)
}
} else if *diskCImage != "" || *diskDImage != "" {
if *sequencerDisk2 {
err := a.AddDisk2Sequencer(5, *diskCImage, *diskDImage, trackTracer)
if err != nil {
panic(err)
}
} else {
err := a.AddDisk2(5, *diskCImage, *diskDImage, trackTracer)
if err != nil {
panic(err)
}
}
}
if *fujinetSlot >= 0 {
a.AddFujinet(*fujinetSlot, *traceSP)
}
if *fastChipCardSlot >= 0 {
a.AddFastChip(*fastChipCardSlot)
}
if *disk2Slot > 0 {
if *sequencerDisk2 {
err := a.AddDisk2Sequencer(*disk2Slot, diskImageFinal, *diskBImage, trackTracer)
if err != nil {
panic(err)
}
} else {
err := a.AddDisk2(*disk2Slot, diskImageFinal, *diskBImage, trackTracer)
if err != nil {
panic(err)
}
}
}
if hardDiskImageFinal != "" {
if *hardDiskSlot <= 0 {
// If there is a hard disk image, but no slot assigned, use slot 7.
*hardDiskSlot = 7
}
err := a.AddSmartPortDisk(*hardDiskSlot, hardDiskImageFinal, *traceHD, *traceSP)
if err != nil {
panic(err)
}
}
if *ramWorksKb != 0 {
if *ramWorksKb%64 != 0 {
panic("Ramworks size must be a multiple of 64")
}
a.AddRAMWorks(*ramWorksKb / 64)
}
if *rgbCard {
a.AddRGBCard()
}
if *nsc == 0 {
a.AddNoSlotClock()
} else if *nsc > 0 {
err := a.AddNoSlotClockInCard(*nsc)
if err != nil {
panic(err)
}
}
if *romX {
err := a.AddRomX()
if err != nil {
panic(err)
}
}
// a.AddCardLogger(4)
return a
}
func initModel(a *Apple2, model string, romFile string, charRomFile string) {
var charGenMap charColumnMap
initialCharGenPage := 0
switch model {
case "2plus":
setApple2plus(a)
if romFile == defaultInternal {
romFile = "<internal>/Apple2_Plus.rom"
}
if charRomFile == defaultInternal {
charRomFile = "<internal>/Apple2rev7CharGen.rom"
}
charGenMap = charGenColumnsMap2Plus
case "2e":
setApple2e(a)
if romFile == defaultInternal {
romFile = "<internal>/Apple2e.rom"
}
if charRomFile == defaultInternal {
charRomFile = "<internal>/Apple IIe Video Unenhanced - 342-0133-A - 2732.bin"
}
charGenMap = charGenColumnsMap2e
case "2enh":
setApple2eEnhanced(a)
if romFile == defaultInternal {
romFile = "<internal>/Apple2e_Enhanced.rom"
}
if charRomFile == defaultInternal {
charRomFile = "<internal>/Apple IIe Video Enhanced - 342-0265-A - 2732.bin"
}
charGenMap = charGenColumnsMap2e
case "base64a":
setBase64a(a)
if romFile == defaultInternal {
err := loadBase64aRom(a)
if err != nil {
panic(err)
}
romFile = ""
}
if charRomFile == defaultInternal {
charRomFile = "<internal>/BASE64A_ROM7_CharGen.BIN"
initialCharGenPage = 1
}
charGenMap = charGenColumnsMapBase64a
default:
panic("Model not supported")
}
// Load ROM
if romFile != "" {
err := a.LoadRom(romFile)
if err != nil {
panic(err)
}
}
// Load character generator
cg, err := newCharacterGenerator(charRomFile, charGenMap, a.isApple2e)
if err != nil {
panic(err)
}
cg.setPage(initialCharGenPage)
a.cg = cg
}

View File

@ -2,21 +2,12 @@ package izapple2
import (
"fmt"
"github.com/ivanizag/iz6502"
)
/*
Copam BASE64A adaptation.
*/
func setBase64a(a *Apple2) {
a.Name = "Base 64A"
a.cpu = iz6502.NewNMOS6502(a.mmu)
addApple2SoftSwitches(a.io)
addBase64aSoftSwitches(a.io)
}
const (
// There are 6 ROM chips. Each can have 4Kb or 8Kb. They can fill
// 2 or 4 banks with 2kb windows.

View File

@ -40,18 +40,19 @@ func (c *cardBase) reset() {
// nothing
}
func (c *cardBase) loadRomFromResource(resource string) {
func (c *cardBase) loadRomFromResource(resource string) error {
data, _, err := LoadResource(resource)
if err != nil {
// The resource should be internal and never fail
panic(err)
return err
}
c.loadRom(data)
return nil
}
func (c *cardBase) loadRom(data []uint8) {
if c.a != nil {
panic("Assert failed. Rom must be loaded before inserting the card in the slot")
panic("Assert failed. ROM must be loaded before inserting the card in the slot")
}
if len(data) == 0x100 {
// Just 256 bytes in Cs00

View File

@ -45,25 +45,30 @@ type CardBrainBoardII struct {
rom []uint8
}
// NewCardBrainBoardII creates a new CardBrainBoardII
func NewCardBrainBoardII() *CardBrainBoardII {
var c CardBrainBoardII
c.name = "Brain Board II"
func newCardBrainBoardIIBuilder() *cardBuilder {
return &cardBuilder{
name: "Brain Board II",
description: "Firmware card for Apple II. It has 4 banks and can be used to boot wozaniam, Integer BASIC or other çustom ROMs.",
defaultParams: &[]paramSpec{
{"rom", "ROM file to load", "<internal>/ApplesoftInteger.BIN"},
{"dip2", "Use the upper half of the ROM", "true"},
},
buildFunc: func(params map[string]string) (Card, error) {
var c CardBrainBoardII
c.highBank = false // Start with wozaniam by default
c.dip2 = paramsGetBool(params, "dip2")
c.highBank = false // Start with wozaniam by default
c.dip2 = true // Use the wozaniam+integer banks
// The ROM has:xaa-wozaniam xab-applesoft xac-wozaniam xad-integer
data, _, err := LoadResource("<internal>/ApplesoftInteger.BIN")
if err != nil {
// The resource should be internal and never fail
panic(err)
// The ROM has:xaa-wozaniam xab-applesoft xac-wozaniam xad-integer
romFile := paramsGetPath(params, "rom")
data, _, err := LoadResource(romFile)
if err != nil {
return nil, err
}
c.rom = data
c.romCxxx = &c
return &c, nil
},
}
c.rom = data
// The ROM of the card is paged as the rest of the ROMs
c.romCxxx = &c
return &c
}
func (c *CardBrainBoardII) assign(a *Apple2, slot int) {

166
cardBuilder.go Normal file
View File

@ -0,0 +1,166 @@
package izapple2
import (
"fmt"
"strconv"
"strings"
"golang.org/x/exp/maps"
)
type paramSpec struct {
name string
description string
defaultValue string
}
type cardBuilder struct {
name string
description string
defaultParams *[]paramSpec
requiresIIe bool
buildFunc func(params map[string]string) (Card, error)
}
const noCardName = "empty"
var cardFactory map[string]*cardBuilder
func getCardFactory() map[string]*cardBuilder {
if cardFactory != nil {
return cardFactory
}
cardFactory = make(map[string]*cardBuilder)
cardFactory["brainboard"] = newCardBrainBoardIIBuilder()
cardFactory["diskii"] = newCardDisk2Builder()
cardFactory["diskiiseq"] = newCardDisk2SequencerBuilder()
cardFactory["fastchip"] = newCardFastChipBuilder()
cardFactory["fujinet"] = newCardSmartPortFujinetBuilder()
cardFactory["inout"] = newCardInOutBuilder()
cardFactory["language"] = newCardLanguageBuilder()
cardFactory["softswitchlogger"] = newCardLoggerBuilder()
cardFactory["memexp"] = newCardMemoryExpansionBuilder()
cardFactory["mouse"] = newCardMouseBuilder()
cardFactory["parallel"] = newCardParallelPrinterBuilder()
cardFactory["saturn"] = newCardSaturnBuilder()
cardFactory["smartport"] = newCardSmartPortStorageBuilder()
cardFactory["swyftcard"] = newCardSwyftBuilder()
cardFactory["thunderclock"] = newCardThunderClockPlusBuilder()
cardFactory["videx"] = newCardVidexBuilder()
cardFactory["vidhd"] = newCardVidHDBuilder()
return cardFactory
}
func availableCards() []string {
return maps.Keys(getCardFactory())
}
func setupCard(a *Apple2, slot int, paramString string) (Card, error) {
paramsArgs := splitConfigurationString(paramString, ',')
cardName := paramsArgs[0]
if cardName == "" || cardName == noCardName {
return nil, nil
}
builder, ok := getCardFactory()[cardName]
if !ok {
return nil, fmt.Errorf("unknown card %s", cardName)
}
if builder.requiresIIe && !a.isApple2e {
return nil, fmt.Errorf("card %s requires an Apple IIe", builder.name)
}
finalParams := make(map[string]string)
if builder.defaultParams != nil {
for _, defaultParam := range *builder.defaultParams {
finalParams[defaultParam.name] = defaultParam.defaultValue
}
}
for i := 1; i < len(paramsArgs); i++ {
paramArgSides := splitConfigurationString(paramsArgs[i], '=')
if _, ok := finalParams[paramArgSides[0]]; !ok {
return nil, fmt.Errorf("unknown parameter %s", paramArgSides[0])
}
if len(paramArgSides) > 2 {
return nil, fmt.Errorf("invalid parameter value for %s", paramArgSides[0])
}
if len(paramArgSides) == 1 {
finalParams[paramArgSides[0]] = "true"
} else {