2019-02-16 19:15:41 +00:00
|
|
|
package apple2
|
|
|
|
|
2019-03-02 17:33:50 +00:00
|
|
|
import (
|
2019-05-05 10:38:24 +00:00
|
|
|
"fmt"
|
2019-03-02 17:33:50 +00:00
|
|
|
"go6502/core6502"
|
2019-05-04 17:49:11 +00:00
|
|
|
"time"
|
2019-03-02 17:33:50 +00:00
|
|
|
)
|
2019-02-16 19:15:41 +00:00
|
|
|
|
2019-03-02 19:41:25 +00:00
|
|
|
// Apple2 represents all the components and state of the emulated machine
|
2019-03-02 17:33:50 +00:00
|
|
|
type Apple2 struct {
|
2019-05-10 16:07:36 +00:00
|
|
|
cpu *core6502.State
|
|
|
|
mmu *memoryManager
|
|
|
|
io *ioC0Page
|
|
|
|
cg *CharacterGenerator
|
|
|
|
cards []cardBase
|
|
|
|
isApple2e bool
|
|
|
|
panicSS bool
|
|
|
|
activeSlot int // Slot that has the addressing 0xc800 to 0ccfff
|
|
|
|
commandChannel chan int
|
|
|
|
cycleDurationNs float64 // Inverse of the cpu clock in Ghz
|
|
|
|
isColor bool
|
|
|
|
fastMode bool
|
|
|
|
fastRequestsCounter int
|
2019-03-02 17:33:50 +00:00
|
|
|
}
|
|
|
|
|
2019-05-04 17:49:11 +00:00
|
|
|
const (
|
|
|
|
// CpuClockMhz is the actual Apple II clock speed
|
|
|
|
CpuClockMhz = 14.318 / 14
|
|
|
|
cpuClockEuroMhz = 14.238 / 14
|
|
|
|
)
|
|
|
|
|
2019-03-02 17:33:50 +00:00
|
|
|
// NewApple2 instantiates an apple2
|
2019-05-10 16:07:36 +00:00
|
|
|
func NewApple2(romFile string, charRomFile string, clockMhz float64,
|
|
|
|
isColor bool, fastMode bool, panicSS bool) *Apple2 {
|
2019-03-02 17:33:50 +00:00
|
|
|
var a Apple2
|
|
|
|
a.mmu = newMemoryManager(&a)
|
|
|
|
a.cpu = core6502.NewNMOS6502(a.mmu)
|
2019-05-16 20:51:04 +00:00
|
|
|
a.mmu.loadRom(romFile)
|
2019-04-21 19:04:02 +00:00
|
|
|
if charRomFile != "" {
|
|
|
|
a.cg = NewCharacterGenerator(charRomFile)
|
|
|
|
}
|
2019-05-04 17:49:11 +00:00
|
|
|
a.commandChannel = make(chan int, 100)
|
2019-05-05 11:25:45 +00:00
|
|
|
a.isColor = isColor
|
2019-05-10 16:07:36 +00:00
|
|
|
a.fastMode = fastMode
|
2019-04-15 21:13:05 +00:00
|
|
|
a.panicSS = panicSS
|
2019-03-02 17:33:50 +00:00
|
|
|
|
2019-05-04 17:49:11 +00:00
|
|
|
if clockMhz <= 0 {
|
|
|
|
// Full speed
|
|
|
|
a.cycleDurationNs = 0
|
|
|
|
} else {
|
|
|
|
a.cycleDurationNs = 1000.0 / clockMhz
|
|
|
|
}
|
|
|
|
|
2019-03-02 17:33:50 +00:00
|
|
|
// Set the io in 0xc000
|
2019-03-02 19:41:25 +00:00
|
|
|
a.io = newIoC0Page(&a)
|
2019-05-16 20:55:19 +00:00
|
|
|
a.mmu.setPages(0xc0, 0xc0, a.io)
|
2019-03-02 17:33:50 +00:00
|
|
|
|
|
|
|
return &a
|
|
|
|
}
|
2019-02-28 22:54:38 +00:00
|
|
|
|
2019-05-09 22:09:15 +00:00
|
|
|
// AddDisk2 insterts a DiskII controller
|
|
|
|
func (a *Apple2) AddDisk2(slot int, diskRomFile string, diskImage string) {
|
2019-03-02 19:41:25 +00:00
|
|
|
d := newCardDisk2(diskRomFile)
|
2019-05-09 22:09:15 +00:00
|
|
|
d.cardBase.insert(a, slot)
|
2019-03-04 23:00:12 +00:00
|
|
|
|
|
|
|
if diskImage != "" {
|
2019-03-10 23:12:34 +00:00
|
|
|
diskette := loadDisquette(diskImage)
|
|
|
|
//diskette.saveNib(diskImage + "bak")
|
|
|
|
d.drive[0].insertDiskette(diskette)
|
2019-03-04 23:00:12 +00:00
|
|
|
}
|
2019-03-02 19:41:25 +00:00
|
|
|
}
|
|
|
|
|
2019-05-16 20:55:19 +00:00
|
|
|
// AddLanguageCard inserts a 16Kb card
|
|
|
|
func (a *Apple2) AddLanguageCard(slot int) {
|
|
|
|
d := newCardLanguage()
|
|
|
|
d.cardBase.insert(a, slot)
|
|
|
|
d.applyState()
|
|
|
|
}
|
|
|
|
|
2019-05-17 07:54:49 +00:00
|
|
|
// AddSaturnCard inserts a 128Kb card
|
|
|
|
func (a *Apple2) AddSaturnCard(slot int) {
|
|
|
|
d := newCardSaturn()
|
|
|
|
d.cardBase.insert(a, slot)
|
|
|
|
d.applyState()
|
|
|
|
}
|
|
|
|
|
2019-05-03 20:00:48 +00:00
|
|
|
// ConfigureStdConsole uses stdin and stdout to interface with the Apple2
|
|
|
|
func (a *Apple2) ConfigureStdConsole(stdinKeyboard bool, stdoutScreen bool) {
|
|
|
|
if !stdinKeyboard && !stdoutScreen {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-03-02 17:33:50 +00:00
|
|
|
// Init frontend
|
2019-04-14 11:06:35 +00:00
|
|
|
fe := newAnsiConsoleFrontend(a, stdinKeyboard)
|
|
|
|
if stdinKeyboard {
|
2019-04-13 18:29:31 +00:00
|
|
|
a.io.setKeyboardProvider(fe)
|
|
|
|
}
|
2019-05-03 20:00:48 +00:00
|
|
|
if stdoutScreen {
|
2019-03-02 19:41:25 +00:00
|
|
|
go fe.textModeGoRoutine()
|
|
|
|
}
|
2019-05-03 20:00:48 +00:00
|
|
|
}
|
2019-02-20 22:51:47 +00:00
|
|
|
|
2019-05-04 17:49:11 +00:00
|
|
|
// SetKeyboardProvider attaches an external keyboard provider
|
|
|
|
func (a *Apple2) SetKeyboardProvider(kb KeyboardProvider) {
|
|
|
|
a.io.setKeyboardProvider(kb)
|
|
|
|
}
|
|
|
|
|
2019-05-09 22:09:15 +00:00
|
|
|
// SetSpeakerProvider attaches an external keyboard provider
|
|
|
|
func (a *Apple2) SetSpeakerProvider(s SpeakerProvider) {
|
|
|
|
a.io.setSpeakerProvider(s)
|
|
|
|
}
|
|
|
|
|
2019-05-05 10:38:24 +00:00
|
|
|
const (
|
|
|
|
// CommandToggleSpeed toggles cpu speed between full speed and actual Apple II speed
|
|
|
|
CommandToggleSpeed = iota + 1
|
2019-05-05 11:25:45 +00:00
|
|
|
// CommandToggleColor toggles between NTSC color TV and Green phospor monitor
|
|
|
|
CommandToggleColor
|
2019-05-05 10:38:24 +00:00
|
|
|
)
|
|
|
|
|
2019-05-04 17:49:11 +00:00
|
|
|
// SendCommand enqueues a command to the emulator thread
|
|
|
|
func (a *Apple2) SendCommand(command int) {
|
|
|
|
a.commandChannel <- command
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *Apple2) executeCommand(command int) {
|
2019-05-05 10:38:24 +00:00
|
|
|
switch command {
|
|
|
|
case CommandToggleSpeed:
|
|
|
|
if a.cycleDurationNs == 0 {
|
|
|
|
fmt.Println("Slow")
|
|
|
|
a.cycleDurationNs = 1000.0 / CpuClockMhz
|
|
|
|
} else {
|
|
|
|
fmt.Println("Fast")
|
|
|
|
a.cycleDurationNs = 0
|
|
|
|
}
|
2019-05-05 11:25:45 +00:00
|
|
|
case CommandToggleColor:
|
|
|
|
a.isColor = !a.isColor
|
2019-05-05 10:38:24 +00:00
|
|
|
}
|
2019-05-04 17:49:11 +00:00
|
|
|
}
|
|
|
|
|
2019-05-10 16:07:36 +00:00
|
|
|
const maxWaitDuration = 100 * time.Millisecond
|
|
|
|
|
2019-05-03 20:00:48 +00:00
|
|
|
// Run starts the Apple2 emulation
|
|
|
|
func (a *Apple2) Run(log bool) {
|
2019-02-16 19:15:41 +00:00
|
|
|
// Start the processor
|
2019-03-02 17:33:50 +00:00
|
|
|
a.cpu.Reset()
|
2019-05-05 10:38:24 +00:00
|
|
|
referenceTime := time.Now()
|
2019-02-17 23:00:39 +00:00
|
|
|
for {
|
2019-05-04 17:49:11 +00:00
|
|
|
// Run a 6502 step
|
2019-03-02 17:33:50 +00:00
|
|
|
a.cpu.ExecuteInstruction(log)
|
|
|
|
|
2019-05-04 17:49:11 +00:00
|
|
|
// Execute meta commands
|
|
|
|
commandsPending := true
|
|
|
|
for commandsPending {
|
|
|
|
select {
|
|
|
|
case command := <-a.commandChannel:
|
|
|
|
a.executeCommand(command)
|
|
|
|
default:
|
|
|
|
commandsPending = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-10 16:07:36 +00:00
|
|
|
if a.cycleDurationNs != 0 && a.fastRequestsCounter <= 0 {
|
2019-05-04 17:49:11 +00:00
|
|
|
// Wait until next 6502 step has to run
|
2019-05-05 10:38:24 +00:00
|
|
|
clockDuration := time.Since(referenceTime)
|
|
|
|
simulatedDuration := time.Duration(float64(a.cpu.GetCycles()) * a.cycleDurationNs)
|
|
|
|
waitDuration := simulatedDuration - clockDuration
|
2019-05-10 16:07:36 +00:00
|
|
|
if waitDuration > maxWaitDuration {
|
2019-05-05 10:38:24 +00:00
|
|
|
// We have to wait too long. Let's fast forward
|
|
|
|
referenceTime = referenceTime.Add(-waitDuration)
|
|
|
|
waitDuration = 0
|
|
|
|
}
|
2019-05-04 17:49:11 +00:00
|
|
|
if waitDuration > 0 {
|
|
|
|
time.Sleep(waitDuration)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-04-13 18:29:31 +00:00
|
|
|
}
|
|
|
|
|
2019-03-02 17:33:50 +00:00
|
|
|
// LoadRom loads a binary file to the top of the memory.
|
|
|
|
const (
|
|
|
|
apple2RomSize = 12 * 1024
|
|
|
|
apple2eRomSize = 16 * 1024
|
|
|
|
)
|
|
|
|
|
2019-05-10 16:07:36 +00:00
|
|
|
func (a *Apple2) requestFastMode() {
|
|
|
|
// Note: if the fastMode is shorter than maxWaitDuration, there won't be any gain.
|
|
|
|
if a.fastMode {
|
|
|
|
a.fastRequestsCounter++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *Apple2) releaseFastMode() {
|
|
|
|
if a.fastMode {
|
|
|
|
a.fastRequestsCounter--
|
|
|
|
}
|
|
|
|
}
|