mirror of
https://github.com/freewilll/apple2-go.git
synced 2025-04-12 03:36:59 +00:00
Made golint happy
This commit is contained in:
parent
01adea08ba
commit
ca7f2d2e04
12
README.md
12
README.md
@ -82,4 +82,14 @@ Assemble the tests
|
||||
* 80 column card
|
||||
* 48k aux memory
|
||||
* double hires
|
||||
* joystick
|
||||
* joystick
|
||||
|
||||
## Coding standards
|
||||
|
||||
Use `gofmt` to ensure standard go style consistency
|
||||
|
||||
go fmt $(go list ./... | grep -v /vendor/)
|
||||
|
||||
Use `golint` to ensure Google's style consistency
|
||||
|
||||
golint $(go list ./...)
|
||||
|
@ -65,7 +65,7 @@ func update(screen *ebiten.Image) error {
|
||||
exitAtBreak := true // Die if a BRK instruction is seen
|
||||
|
||||
// Run for 1/60 of a second, the duration of an ebiten frame
|
||||
cpu.Run(*showInstructions, breakAddress, exitAtBreak, *disableFirmwareWait, system.CpuFrequency/60)
|
||||
cpu.Run(*showInstructions, breakAddress, exitAtBreak, *disableFirmwareWait, system.CPUFrequency/60)
|
||||
|
||||
// Process any audio speaker clicks from this frame
|
||||
audio.ForwardToFrameCycle()
|
||||
|
@ -18,10 +18,11 @@ func Click() {
|
||||
func attenuate(sample int16) int16 {
|
||||
if system.AudioAttenuationCounter == 0 {
|
||||
return 0
|
||||
} else {
|
||||
system.AudioAttenuationCounter--
|
||||
return sample
|
||||
}
|
||||
|
||||
system.AudioAttenuationCounter--
|
||||
return sample
|
||||
|
||||
}
|
||||
|
||||
// ForwardToFrameCycle calculates how many audio samples need to be written to
|
||||
@ -29,7 +30,7 @@ func attenuate(sample int16) int16 {
|
||||
// flush and shove them into the channel.
|
||||
func ForwardToFrameCycle() {
|
||||
// 1023000/44100=23.19 cycles per audio sample
|
||||
cyclesPerAudioSample := system.CpuFrequency / float64(system.AudioSampleRate)
|
||||
cyclesPerAudioSample := system.CPUFrequency / float64(system.AudioSampleRate)
|
||||
|
||||
// Should be about 1023000/60=17050
|
||||
elapsedCycles := system.FrameCycles - system.LastAudioCycles
|
||||
|
@ -10,11 +10,17 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
audioContext *ebiten_audio.Context // Ebitem audio context
|
||||
player *ebiten_audio.Player // Ebitem stream player
|
||||
firstAudio bool // True at startup
|
||||
Mute bool // Mute
|
||||
ClickWhenDriveHeadMoves bool // Click speaker when the drive head moves
|
||||
audioContext *ebiten_audio.Context // Ebitem audio context
|
||||
player *ebiten_audio.Player // Ebitem stream player
|
||||
firstAudio bool // True at startup
|
||||
)
|
||||
|
||||
var (
|
||||
// Mute ensures no samples are output
|
||||
Mute bool
|
||||
|
||||
// ClickWhenDriveHeadMoves makes the speaker click once every time the stepper motor magnets change
|
||||
ClickWhenDriveHeadMoves bool
|
||||
)
|
||||
|
||||
// The streaming code is based on the ebiten sinewave example
|
||||
|
@ -11,9 +11,9 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func assertMemoryConfiguration(t *testing.T, address uint16, upperRamReadOnly bool, upperReadMappedToROM bool, d000Bank int) {
|
||||
func assertMemoryConfiguration(t *testing.T, address uint16, upperRAMReadOnly bool, upperReadMappedToROM bool, d000Bank int) {
|
||||
mmu.WriteMemory(address, 0x00)
|
||||
assert.Equal(t, upperRamReadOnly, mmu.UpperRamReadOnly)
|
||||
assert.Equal(t, upperRAMReadOnly, mmu.UpperRAMReadOnly)
|
||||
assert.Equal(t, upperReadMappedToROM, mmu.UpperReadMappedToROM)
|
||||
assert.Equal(t, d000Bank, mmu.D000Bank)
|
||||
}
|
||||
@ -67,7 +67,7 @@ func TestBankSwitching(t *testing.T) {
|
||||
|
||||
// Set d000 RAM to bank 1, RAM to read only and attempt writes
|
||||
mmu.SetD000Bank(1)
|
||||
mmu.SetUpperRamReadOnly(true)
|
||||
mmu.SetUpperRAMReadOnly(true)
|
||||
assert.Equal(t, uint8(0xfd), mmu.PhysicalMemory.MainMemory[0xc000]) // bank #1 RAM
|
||||
assert.Equal(t, uint8(0xfe), mmu.PhysicalMemory.MainMemory[0xd000]) // bank #2 RAM
|
||||
mmu.WriteMemory(0xd000, 0x01) // attempt to write to read only RAM
|
||||
@ -77,7 +77,7 @@ func TestBankSwitching(t *testing.T) {
|
||||
assert.Equal(t, uint8(0xff), mmu.PhysicalMemory.MainMemory[0xffff]) // top of RAM is unchanged
|
||||
|
||||
// Set RAM to write and write to it
|
||||
mmu.SetUpperRamReadOnly(false)
|
||||
mmu.SetUpperRAMReadOnly(false)
|
||||
mmu.WriteMemory(0xd000, 0xfc) // write to RAM
|
||||
mmu.WriteMemory(0xffff, 0xfb) // write to RAM
|
||||
assert.Equal(t, uint8(0xfc), mmu.PhysicalMemory.MainMemory[0xc000]) // bank #1 RAM has been updated
|
||||
|
@ -24,7 +24,7 @@ func testBellCycles(delay int) {
|
||||
exitAtBreak := false
|
||||
disableFirmwareWait := false
|
||||
cpu.State.PC = 0x800
|
||||
cpu.Run(showInstructions, &breakAddress, exitAtBreak, disableFirmwareWait, system.CpuFrequency*1000)
|
||||
cpu.Run(showInstructions, &breakAddress, exitAtBreak, disableFirmwareWait, system.CPUFrequency*1000)
|
||||
|
||||
// See http://apple2.org.za/gswv/a2zine/GS.WorldView/Resources/USEFUL.TABLES/WAIT.DELAY.CR.txt
|
||||
expectedCycles := (26 + 27*delay + 5*delay*delay) / 2
|
||||
|
89
cpu/cpu.go
89
cpu/cpu.go
@ -9,16 +9,17 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
CpuFlagC byte = 1 << iota // 0x01 carry
|
||||
CpuFlagZ // 0x02 zero
|
||||
CpuFlagI // 0x04 interrupt disable
|
||||
CpuFlagD // 0x08 decimal mode
|
||||
CpuFlagB // 0x10 break
|
||||
CpuFlagR // 0x20 reserved (unused)
|
||||
CpuFlagV // 0x40 overflow
|
||||
CpuFlagN // 0x80 sign/negative
|
||||
cpuFlagC byte = 1 << iota // 0x01 carry
|
||||
cpuFlagZ // 0x02 zero
|
||||
cpuFlagI // 0x04 interrupt disable
|
||||
cpuFlagD // 0x08 decimal mode
|
||||
cpuFlagB // 0x10 break
|
||||
cpuFlagR // 0x20 reserved (unused)
|
||||
cpuFlagV // 0x40 overflow
|
||||
cpuFlagN // 0x80 sign/negative
|
||||
)
|
||||
|
||||
// State contains the CPU state
|
||||
var State struct {
|
||||
A uint8 // accumulator
|
||||
X uint8 // X register
|
||||
@ -40,70 +41,70 @@ func Init() {
|
||||
State.A = 0
|
||||
State.X = 0
|
||||
State.Y = 0
|
||||
State.P = CpuFlagR | CpuFlagB | CpuFlagZ
|
||||
State.P = cpuFlagR | cpuFlagB | cpuFlagZ
|
||||
State.SP = 0xff
|
||||
}
|
||||
|
||||
// setC sets the carry flag
|
||||
func setC(value bool) {
|
||||
if value {
|
||||
State.P |= CpuFlagC
|
||||
State.P |= cpuFlagC
|
||||
} else {
|
||||
State.P &= ^CpuFlagC
|
||||
State.P &= ^cpuFlagC
|
||||
}
|
||||
}
|
||||
|
||||
// setV sets the overflow flag
|
||||
func setV(value bool) {
|
||||
if value {
|
||||
State.P |= CpuFlagV
|
||||
State.P |= cpuFlagV
|
||||
} else {
|
||||
State.P &= ^CpuFlagV
|
||||
State.P &= ^cpuFlagV
|
||||
}
|
||||
}
|
||||
|
||||
// setN sets the sign/negative flag if the value is negative (>=0x80)
|
||||
func setN(value uint8) {
|
||||
if (value & 0x80) != 0 {
|
||||
State.P |= CpuFlagN
|
||||
State.P |= cpuFlagN
|
||||
} else {
|
||||
State.P &= ^CpuFlagN
|
||||
State.P &= ^cpuFlagN
|
||||
}
|
||||
}
|
||||
|
||||
// setZ sets the zero flag if the value is zero
|
||||
func setZ(value uint8) {
|
||||
if value == 0 {
|
||||
State.P |= CpuFlagZ
|
||||
State.P |= cpuFlagZ
|
||||
} else {
|
||||
State.P &= ^CpuFlagZ
|
||||
State.P &= ^cpuFlagZ
|
||||
}
|
||||
}
|
||||
|
||||
func isC() bool {
|
||||
return (State.P & CpuFlagC) != 0
|
||||
return (State.P & cpuFlagC) != 0
|
||||
}
|
||||
|
||||
func isZ() bool {
|
||||
return (State.P & CpuFlagZ) != 0
|
||||
return (State.P & cpuFlagZ) != 0
|
||||
}
|
||||
|
||||
func isD() bool {
|
||||
return (State.P & CpuFlagD) != 0
|
||||
return (State.P & cpuFlagD) != 0
|
||||
}
|
||||
|
||||
func isV() bool {
|
||||
return (State.P & CpuFlagV) != 0
|
||||
return (State.P & cpuFlagV) != 0
|
||||
}
|
||||
|
||||
func isN() bool {
|
||||
return (State.P & CpuFlagN) != 0
|
||||
return (State.P & cpuFlagN) != 0
|
||||
}
|
||||
|
||||
// push8 pushes an 8 bit value to the stack
|
||||
func push8(value uint8) {
|
||||
mmu.WritePageTable[mmu.StackPage][State.SP] = value
|
||||
State.SP -= 1
|
||||
State.SP--
|
||||
State.SP &= 0xff
|
||||
}
|
||||
|
||||
@ -117,7 +118,7 @@ func push16(value uint16) {
|
||||
|
||||
// pop8 pulls an 8 bit value from the stack
|
||||
func pop8() uint8 {
|
||||
State.SP += 1
|
||||
State.SP++
|
||||
State.SP &= 0xff
|
||||
return mmu.ReadPageTable[mmu.StackPage][State.SP]
|
||||
}
|
||||
@ -153,7 +154,7 @@ func branch(doBranch bool) {
|
||||
// The number of cycles depends on if a page boundary was crossed
|
||||
samePage := (State.PC & 0xff00) == (relativeAddress & 0xff00)
|
||||
if samePage {
|
||||
system.FrameCycles += 1
|
||||
system.FrameCycles++
|
||||
} else {
|
||||
system.FrameCycles += 2
|
||||
}
|
||||
@ -479,7 +480,7 @@ func postProcessShift(addressMode byte, address uint16, value uint8) {
|
||||
switch addressMode {
|
||||
case amAccumulator:
|
||||
State.A = value
|
||||
State.PC += 1
|
||||
State.PC++
|
||||
system.FrameCycles += 2
|
||||
case amZeroPage:
|
||||
mmu.WriteMemory(address, value)
|
||||
@ -523,27 +524,27 @@ func postProcessIncDec(addressMode byte) {
|
||||
|
||||
func brk() {
|
||||
push16(State.PC + 2)
|
||||
State.P |= CpuFlagB
|
||||
State.P |= cpuFlagB
|
||||
push8(State.P)
|
||||
State.P |= CpuFlagI
|
||||
State.P |= cpuFlagI
|
||||
State.PC = uint16(mmu.ReadMemory(0xffff))<<8 + uint16(mmu.ReadMemory(0xfffe))
|
||||
system.FrameCycles += 7
|
||||
}
|
||||
|
||||
func irq() {
|
||||
push16(State.PC)
|
||||
State.P &= ^CpuFlagB
|
||||
State.P &= ^cpuFlagB
|
||||
push8(State.P)
|
||||
State.P |= CpuFlagI
|
||||
State.P |= cpuFlagI
|
||||
State.PC = uint16(mmu.ReadMemory(0xffff))<<8 + uint16(mmu.ReadMemory(0xfffe))
|
||||
system.FrameCycles += 7
|
||||
}
|
||||
|
||||
func nmi() {
|
||||
push16(State.PC)
|
||||
State.P &= ^CpuFlagB
|
||||
State.P &= ^cpuFlagB
|
||||
push8(State.P)
|
||||
State.P |= CpuFlagI
|
||||
State.P |= cpuFlagI
|
||||
State.PC = uint16(mmu.ReadMemory(0xfffb))<<8 + uint16(mmu.ReadMemory(0xfffa))
|
||||
system.FrameCycles += 7
|
||||
}
|
||||
@ -572,7 +573,7 @@ func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableF
|
||||
}
|
||||
|
||||
// Handle an IRQ f there is one pending and interrupts are enabled
|
||||
if system.PendingInterrupt && ((State.P & CpuFlagI) == 0) {
|
||||
if system.PendingInterrupt && ((State.P & cpuFlagI) == 0) {
|
||||
irq()
|
||||
system.PendingInterrupt = false
|
||||
continue
|
||||
@ -761,23 +762,23 @@ func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableF
|
||||
State.PC++
|
||||
system.FrameCycles += 2
|
||||
case 0x58: // CLI
|
||||
State.P &= ^CpuFlagI
|
||||
State.P &= ^cpuFlagI
|
||||
State.PC++
|
||||
system.FrameCycles += 2
|
||||
case 0x78: // SEI
|
||||
State.P |= CpuFlagI
|
||||
State.P |= cpuFlagI
|
||||
State.PC++
|
||||
system.FrameCycles += 2
|
||||
case 0xb8: // CLV
|
||||
State.P &= ^CpuFlagV
|
||||
State.P &= ^cpuFlagV
|
||||
State.PC++
|
||||
system.FrameCycles += 2
|
||||
case 0xd8: // CLD
|
||||
State.P &= ^CpuFlagD
|
||||
State.P &= ^cpuFlagD
|
||||
State.PC++
|
||||
system.FrameCycles += 2
|
||||
case 0xf8: //SED
|
||||
State.P |= CpuFlagD
|
||||
State.P |= cpuFlagD
|
||||
State.PC++
|
||||
system.FrameCycles += 2
|
||||
|
||||
@ -795,12 +796,12 @@ func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableF
|
||||
case 0x08: // PHP
|
||||
// From http://visual6502.org/wiki/index.php?title=6502_BRK_and_B_bit#the_B_flag_and_the_various_mechanisms
|
||||
// software instructions BRK & PHP will push the B flag as being 1.
|
||||
push8(State.P | CpuFlagB)
|
||||
push8(State.P | cpuFlagB)
|
||||
State.PC++
|
||||
system.FrameCycles += 3
|
||||
case 0x28: // PLP
|
||||
// CpuFlagR is always supposed to be 1
|
||||
State.P = pop8() | CpuFlagR
|
||||
// cpuFlagR is always supposed to be 1
|
||||
State.P = pop8() | cpuFlagR
|
||||
State.PC++
|
||||
system.FrameCycles += 4
|
||||
case 0xea:
|
||||
@ -810,7 +811,7 @@ func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableF
|
||||
case 0x00: // BRK
|
||||
brk()
|
||||
case 0x40: // RTI
|
||||
State.P = pop8() | CpuFlagR
|
||||
State.P = pop8() | cpuFlagR
|
||||
value := pop16()
|
||||
State.PC = value
|
||||
system.FrameCycles += 6
|
||||
@ -845,7 +846,7 @@ func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableF
|
||||
address, value := preProcessShift(addressMode)
|
||||
value16 := uint16(value)
|
||||
value16 <<= 1
|
||||
if (State.P & CpuFlagC) != 0 {
|
||||
if (State.P & cpuFlagC) != 0 {
|
||||
value16 |= 0x01
|
||||
}
|
||||
setC((value16 & 0x100) != 0)
|
||||
@ -856,7 +857,7 @@ func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableF
|
||||
case 0x6a, 0x66, 0x76, 0x6e, 0x7e: // ROR
|
||||
address, value := preProcessShift(addressMode)
|
||||
value16 := uint16(value)
|
||||
if (State.P & CpuFlagC) != 0 {
|
||||
if (State.P & cpuFlagC) != 0 {
|
||||
value16 |= 0x100
|
||||
}
|
||||
setC((value16 & 0x01) != 0)
|
||||
|
16
cpu/debug.go
16
cpu/debug.go
@ -29,14 +29,14 @@ func printInstruction(instruction string, showRegisters bool) {
|
||||
State.P,
|
||||
)
|
||||
|
||||
printFlag(State.P, CpuFlagN, "n")
|
||||
printFlag(State.P, CpuFlagV, "v")
|
||||
fmt.Print("-") // CpuFlagR flag that's always 1
|
||||
printFlag(State.P, CpuFlagB, "b")
|
||||
printFlag(State.P, CpuFlagD, "d")
|
||||
printFlag(State.P, CpuFlagI, "i")
|
||||
printFlag(State.P, CpuFlagZ, "z")
|
||||
printFlag(State.P, CpuFlagC, "c")
|
||||
printFlag(State.P, cpuFlagN, "n")
|
||||
printFlag(State.P, cpuFlagV, "v")
|
||||
fmt.Print("-") // cpuFlagR flag that's always 1
|
||||
printFlag(State.P, cpuFlagB, "b")
|
||||
printFlag(State.P, cpuFlagD, "d")
|
||||
printFlag(State.P, cpuFlagI, "i")
|
||||
printFlag(State.P, cpuFlagZ, "z")
|
||||
printFlag(State.P, cpuFlagC, "c")
|
||||
}
|
||||
|
||||
fmt.Println("")
|
||||
|
@ -311,6 +311,7 @@ func initOpCodes() {
|
||||
opCodes[0xFF] = opCode{mnemonic: "???", addressingMode: addressingModes[amExpansion]}
|
||||
}
|
||||
|
||||
// InitInstructionDecoder initializes tables used for the instruction decoding
|
||||
func InitInstructionDecoder() {
|
||||
initAddressingModes()
|
||||
initOpCodes()
|
||||
|
@ -123,7 +123,7 @@ func InitDiskImage() {
|
||||
resetsectorWriteState()
|
||||
}
|
||||
|
||||
// InitDiskImage reads a disk image from file
|
||||
// ReadDiskImage reads a disk image from file
|
||||
func ReadDiskImage(path string) {
|
||||
imagePath = path
|
||||
|
||||
@ -329,7 +329,7 @@ func decodeAddressField(data []uint8) addressField {
|
||||
return af
|
||||
}
|
||||
|
||||
// Read a byte from the disk head and spin the disk along
|
||||
// ReadTrackData reads a byte from the disk head and spins the disk along
|
||||
func ReadTrackData() (result uint8) {
|
||||
result = trackData[system.DriveState.BytePosition]
|
||||
|
||||
@ -377,7 +377,7 @@ func WriteTrackData(value uint8) {
|
||||
}
|
||||
|
||||
sectorWriteState.RawData[sectorWriteState.RawDataPosition] = value
|
||||
sectorWriteState.RawDataPosition += 1
|
||||
sectorWriteState.RawDataPosition++
|
||||
|
||||
// Check for address prologue
|
||||
if sectorWriteState.RawDataPosition > 2 && sectorWriteState.RawData[sectorWriteState.RawDataPosition-3] == 0xd5 &&
|
||||
@ -393,7 +393,7 @@ func WriteTrackData(value uint8) {
|
||||
|
||||
} else if sectorWriteState.State == receivingData {
|
||||
sectorWriteState.RawData[sectorWriteState.RawDataPosition] = value
|
||||
sectorWriteState.RawDataPosition += 1
|
||||
sectorWriteState.RawDataPosition++
|
||||
|
||||
if sectorWriteState.RawDataPosition == 0x56+0x100 {
|
||||
// We have the full sector data
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
)
|
||||
|
||||
var ebitenAsciiMap map[ebiten.Key]uint8 // ebiten keys mapped to ASCII
|
||||
var ebitenASCIIMap map[ebiten.Key]uint8 // ebiten keys mapped to ASCII
|
||||
var shiftMap map[uint8]uint8 // ebiten keys mapped to ASCII when shift is pressed
|
||||
var controlMap map[uint8]uint8 // ebiten keys mapped to ASCII when control is pressed
|
||||
|
||||
@ -19,68 +19,68 @@ func Init() {
|
||||
strobe = 0
|
||||
capsLock = true
|
||||
|
||||
ebitenAsciiMap = make(map[ebiten.Key]uint8)
|
||||
ebitenASCIIMap = make(map[ebiten.Key]uint8)
|
||||
shiftMap = make(map[uint8]uint8)
|
||||
controlMap = make(map[uint8]uint8)
|
||||
previousKeysPressed = make(map[uint8]bool)
|
||||
|
||||
ebitenAsciiMap[ebiten.KeyLeft] = 8
|
||||
ebitenAsciiMap[ebiten.KeyTab] = 9
|
||||
ebitenAsciiMap[ebiten.KeyDown] = 10
|
||||
ebitenAsciiMap[ebiten.KeyUp] = 11
|
||||
ebitenAsciiMap[ebiten.KeyEnter] = 13
|
||||
ebitenAsciiMap[ebiten.KeyRight] = 21
|
||||
ebitenAsciiMap[ebiten.KeyEscape] = 27
|
||||
ebitenAsciiMap[ebiten.KeyDelete] = 127
|
||||
ebitenASCIIMap[ebiten.KeyLeft] = 8
|
||||
ebitenASCIIMap[ebiten.KeyTab] = 9
|
||||
ebitenASCIIMap[ebiten.KeyDown] = 10
|
||||
ebitenASCIIMap[ebiten.KeyUp] = 11
|
||||
ebitenASCIIMap[ebiten.KeyEnter] = 13
|
||||
ebitenASCIIMap[ebiten.KeyRight] = 21
|
||||
ebitenASCIIMap[ebiten.KeyEscape] = 27
|
||||
ebitenASCIIMap[ebiten.KeyDelete] = 127
|
||||
|
||||
ebitenAsciiMap[ebiten.Key0] = '0'
|
||||
ebitenAsciiMap[ebiten.Key1] = '1'
|
||||
ebitenAsciiMap[ebiten.Key2] = '2'
|
||||
ebitenAsciiMap[ebiten.Key3] = '3'
|
||||
ebitenAsciiMap[ebiten.Key4] = '4'
|
||||
ebitenAsciiMap[ebiten.Key5] = '5'
|
||||
ebitenAsciiMap[ebiten.Key6] = '6'
|
||||
ebitenAsciiMap[ebiten.Key7] = '7'
|
||||
ebitenAsciiMap[ebiten.Key8] = '8'
|
||||
ebitenAsciiMap[ebiten.Key9] = '9'
|
||||
ebitenAsciiMap[ebiten.KeyA] = 'a'
|
||||
ebitenAsciiMap[ebiten.KeyB] = 'b'
|
||||
ebitenAsciiMap[ebiten.KeyC] = 'c'
|
||||
ebitenAsciiMap[ebiten.KeyD] = 'd'
|
||||
ebitenAsciiMap[ebiten.KeyE] = 'e'
|
||||
ebitenAsciiMap[ebiten.KeyF] = 'f'
|
||||
ebitenAsciiMap[ebiten.KeyG] = 'g'
|
||||
ebitenAsciiMap[ebiten.KeyH] = 'h'
|
||||
ebitenAsciiMap[ebiten.KeyI] = 'i'
|
||||
ebitenAsciiMap[ebiten.KeyJ] = 'j'
|
||||
ebitenAsciiMap[ebiten.KeyK] = 'k'
|
||||
ebitenAsciiMap[ebiten.KeyL] = 'l'
|
||||
ebitenAsciiMap[ebiten.KeyM] = 'm'
|
||||
ebitenAsciiMap[ebiten.KeyN] = 'n'
|
||||
ebitenAsciiMap[ebiten.KeyO] = 'o'
|
||||
ebitenAsciiMap[ebiten.KeyP] = 'p'
|
||||
ebitenAsciiMap[ebiten.KeyQ] = 'q'
|
||||
ebitenAsciiMap[ebiten.KeyR] = 'r'
|
||||
ebitenAsciiMap[ebiten.KeyS] = 's'
|
||||
ebitenAsciiMap[ebiten.KeyT] = 't'
|
||||
ebitenAsciiMap[ebiten.KeyU] = 'u'
|
||||
ebitenAsciiMap[ebiten.KeyV] = 'v'
|
||||
ebitenAsciiMap[ebiten.KeyW] = 'w'
|
||||
ebitenAsciiMap[ebiten.KeyX] = 'x'
|
||||
ebitenAsciiMap[ebiten.KeyY] = 'y'
|
||||
ebitenAsciiMap[ebiten.KeyZ] = 'z'
|
||||
ebitenAsciiMap[ebiten.KeyApostrophe] = '\''
|
||||
ebitenAsciiMap[ebiten.KeyBackslash] = '\\'
|
||||
ebitenAsciiMap[ebiten.KeyComma] = ','
|
||||
ebitenAsciiMap[ebiten.KeyEqual] = '='
|
||||
ebitenAsciiMap[ebiten.KeyGraveAccent] = '`'
|
||||
ebitenAsciiMap[ebiten.KeyLeftBracket] = '['
|
||||
ebitenAsciiMap[ebiten.KeyMinus] = '-'
|
||||
ebitenAsciiMap[ebiten.KeyPeriod] = '.'
|
||||
ebitenAsciiMap[ebiten.KeyRightBracket] = ']'
|
||||
ebitenAsciiMap[ebiten.KeySemicolon] = ';'
|
||||
ebitenAsciiMap[ebiten.KeySlash] = '/'
|
||||
ebitenAsciiMap[ebiten.KeySpace] = ' '
|
||||
ebitenASCIIMap[ebiten.Key0] = '0'
|
||||
ebitenASCIIMap[ebiten.Key1] = '1'
|
||||
ebitenASCIIMap[ebiten.Key2] = '2'
|
||||
ebitenASCIIMap[ebiten.Key3] = '3'
|
||||
ebitenASCIIMap[ebiten.Key4] = '4'
|
||||
ebitenASCIIMap[ebiten.Key5] = '5'
|
||||
ebitenASCIIMap[ebiten.Key6] = '6'
|
||||
ebitenASCIIMap[ebiten.Key7] = '7'
|
||||
ebitenASCIIMap[ebiten.Key8] = '8'
|
||||
ebitenASCIIMap[ebiten.Key9] = '9'
|
||||
ebitenASCIIMap[ebiten.KeyA] = 'a'
|
||||
ebitenASCIIMap[ebiten.KeyB] = 'b'
|
||||
ebitenASCIIMap[ebiten.KeyC] = 'c'
|
||||
ebitenASCIIMap[ebiten.KeyD] = 'd'
|
||||
ebitenASCIIMap[ebiten.KeyE] = 'e'
|
||||
ebitenASCIIMap[ebiten.KeyF] = 'f'
|
||||
ebitenASCIIMap[ebiten.KeyG] = 'g'
|
||||
ebitenASCIIMap[ebiten.KeyH] = 'h'
|
||||
ebitenASCIIMap[ebiten.KeyI] = 'i'
|
||||
ebitenASCIIMap[ebiten.KeyJ] = 'j'
|
||||
ebitenASCIIMap[ebiten.KeyK] = 'k'
|
||||
ebitenASCIIMap[ebiten.KeyL] = 'l'
|
||||
ebitenASCIIMap[ebiten.KeyM] = 'm'
|
||||
ebitenASCIIMap[ebiten.KeyN] = 'n'
|
||||
ebitenASCIIMap[ebiten.KeyO] = 'o'
|
||||
ebitenASCIIMap[ebiten.KeyP] = 'p'
|
||||
ebitenASCIIMap[ebiten.KeyQ] = 'q'
|
||||
ebitenASCIIMap[ebiten.KeyR] = 'r'
|
||||
ebitenASCIIMap[ebiten.KeyS] = 's'
|
||||
ebitenASCIIMap[ebiten.KeyT] = 't'
|
||||
ebitenASCIIMap[ebiten.KeyU] = 'u'
|
||||
ebitenASCIIMap[ebiten.KeyV] = 'v'
|
||||
ebitenASCIIMap[ebiten.KeyW] = 'w'
|
||||
ebitenASCIIMap[ebiten.KeyX] = 'x'
|
||||
ebitenASCIIMap[ebiten.KeyY] = 'y'
|
||||
ebitenASCIIMap[ebiten.KeyZ] = 'z'
|
||||
ebitenASCIIMap[ebiten.KeyApostrophe] = '\''
|
||||
ebitenASCIIMap[ebiten.KeyBackslash] = '\\'
|
||||
ebitenASCIIMap[ebiten.KeyComma] = ','
|
||||
ebitenASCIIMap[ebiten.KeyEqual] = '='
|
||||
ebitenASCIIMap[ebiten.KeyGraveAccent] = '`'
|
||||
ebitenASCIIMap[ebiten.KeyLeftBracket] = '['
|
||||
ebitenASCIIMap[ebiten.KeyMinus] = '-'
|
||||
ebitenASCIIMap[ebiten.KeyPeriod] = '.'
|
||||
ebitenASCIIMap[ebiten.KeyRightBracket] = ']'
|
||||
ebitenASCIIMap[ebiten.KeySemicolon] = ';'
|
||||
ebitenASCIIMap[ebiten.KeySlash] = '/'
|
||||
ebitenASCIIMap[ebiten.KeySpace] = ' '
|
||||
|
||||
shiftMap['1'] = '!'
|
||||
shiftMap['2'] = '@'
|
||||
@ -198,7 +198,7 @@ func Poll() {
|
||||
newKeysPressed := make(map[uint8]bool)
|
||||
|
||||
// Query ebiten for all possible keys
|
||||
for k, v := range ebitenAsciiMap {
|
||||
for k, v := range ebitenASCIIMap {
|
||||
if ebiten.IsKeyPressed(k) {
|
||||
allKeysPressed[v] = true
|
||||
|
||||
|
269
mmu/io.go
269
mmu/io.go
@ -14,83 +14,83 @@ import (
|
||||
// https://github.com/cmosher01/Apple-II-Platform/blob/master/asminclude/iorom.asm
|
||||
|
||||
const (
|
||||
KEYBOARD = 0xC000 // keyboard data (latched) (RD-only)
|
||||
CLR80COL = 0xC000 // use 80-column memory mapping (WR-only)
|
||||
SET80COL = 0xC001
|
||||
CLRAUXRD = 0xC002 // read from auxilliary 48K
|
||||
SETAUXRD = 0xC003
|
||||
CLRAUXWR = 0xC004 // write to auxilliary 48K
|
||||
SETAUXWR = 0xC005
|
||||
CLRCXROM = 0xC006 // use external slot ROM
|
||||
SETCXROM = 0xC007
|
||||
CLRAUXZP = 0xC008 // use auxilliary ZP, stack, & LC
|
||||
SETAUXZP = 0xC009
|
||||
CLRC3ROM = 0xC00A // use external slot C3 ROM
|
||||
SETC3ROM = 0xC00B
|
||||
CLR80VID = 0xC00C // use 80-column display mode
|
||||
SET80VID = 0xC00D
|
||||
CLRALTCH = 0xC00E // use alternate character set ROM
|
||||
SETALTCH = 0xC00F
|
||||
STROBE = 0xC010 // strobe (unlatch) keyboard data
|
||||
mKEYBOARD = 0xC000 // keyboard data (latched) (RD-only)
|
||||
mCLR80COL = 0xC000 // use 80-column memory mapping (WR-only)
|
||||
mSET80COL = 0xC001
|
||||
mCLRAUXRD = 0xC002 // read from auxilliary 48K
|
||||
mSETAUXRD = 0xC003
|
||||
mCLRAUXWR = 0xC004 // write to auxilliary 48K
|
||||
mSETAUXWR = 0xC005
|
||||
mCLRCXROM = 0xC006 // use external slot ROM
|
||||
mSETCXROM = 0xC007
|
||||
mCLRAUXZP = 0xC008 // use auxilliary ZP, stack, & LC
|
||||
mSETAUXZP = 0xC009
|
||||
mCLRC3ROM = 0xC00A // use external slot C3 ROM
|
||||
mSETC3ROM = 0xC00B
|
||||
mCLR80VID = 0xC00C // use 80-column display mode
|
||||
mSET80VID = 0xC00D
|
||||
mCLRALTCH = 0xC00E // use alternate character set ROM
|
||||
mSETALTCH = 0xC00F
|
||||
mSTROBE = 0xC010 // strobe (unlatch) keyboard data
|
||||
|
||||
RDLCBNK2 = 0xC011 // reading from LC bank $Dx 2
|
||||
RDLCRAM = 0xC012 // reading from LC RAM
|
||||
RDRAMRD = 0xC013 // reading from auxilliary 48K
|
||||
RDRAMWR = 0xC014 // writing to auxilliary 48K
|
||||
RDCXROM = 0xC015 // using external slot ROM
|
||||
RDAUXZP = 0xC016 // using auxilliary ZP, stack, & LC
|
||||
RDC3ROM = 0xC017 // using external slot C3 ROM
|
||||
RD80COL = 0xC018 // using 80-column memory mapping
|
||||
RDVBLBAR = 0xC019 // not VBL (VBL signal low)
|
||||
RDTEXT = 0xC01A // using text mode
|
||||
RDMIXED = 0xC01B // using mixed mode
|
||||
RDPAGE2 = 0xC01C // using text/graphics page2
|
||||
RDHIRES = 0xC01D // using Hi-res graphics mode
|
||||
RDALTCH = 0xC01E // using alternate character set ROM
|
||||
RD80VID = 0xC01F // using 80-column display mode
|
||||
SPEAKER = 0xC030 // toggle speaker diaphragm
|
||||
mRDLCBNK2 = 0xC011 // reading from LC bank $Dx 2
|
||||
mRDLCRAM = 0xC012 // reading from LC RAM
|
||||
mRDRAMRD = 0xC013 // reading from auxilliary 48K
|
||||
mRDRAMWR = 0xC014 // writing to auxilliary 48K
|
||||
mRDCXROM = 0xC015 // using external slot ROM
|
||||
mRDAUXZP = 0xC016 // using auxilliary ZP, stack, & LC
|
||||
mRDC3ROM = 0xC017 // using external slot C3 ROM
|
||||
mRD80COL = 0xC018 // using 80-column memory mapping
|
||||
mRDVBLBAR = 0xC019 // not VBL (VBL signal low)
|
||||
mRDTEXT = 0xC01A // using text mode
|
||||
mRDMIXED = 0xC01B // using mixed mode
|
||||
mRDPAGE2 = 0xC01C // using text/graphics page2
|
||||
mRDHIRES = 0xC01D // using Hi-res graphics mode
|
||||
mRDALTCH = 0xC01E // using alternate character set ROM
|
||||
mRD80VID = 0xC01F // using 80-column display mode
|
||||
mSPEAKER = 0xC030 // toggle speaker diaphragm
|
||||
|
||||
CLRTEXT = 0xC050 // enable text-only mode
|
||||
SETTEXT = 0xC051
|
||||
CLRMIXED = 0xC052 // enable graphics/text mixed mode
|
||||
SETMIXED = 0xC053
|
||||
TXTPAGE1 = 0xC054 // select page1/2 (or page1/1x)
|
||||
TXTPAGE2 = 0xC055
|
||||
CLRHIRES = 0xC056 // enable Hi-res graphics
|
||||
SETHIRES = 0xC057
|
||||
mCLRTEXT = 0xC050 // enable text-only mode
|
||||
mSETTEXT = 0xC051
|
||||
mCLRMIXED = 0xC052 // enable graphics/text mixed mode
|
||||
mSETMIXED = 0xC053
|
||||
mTXTPAGE1 = 0xC054 // select page1/2 (or page1/1x)
|
||||
mTXTPAGE2 = 0xC055
|
||||
mCLRHIRES = 0xC056 // enable Hi-res graphics
|
||||
mSETHIRES = 0xC057
|
||||
|
||||
SETAN0 = 0xC058 // 4-bit annunciator inputs
|
||||
CLRAN0 = 0xC059
|
||||
SETAN1 = 0xC05A
|
||||
CLRAN1 = 0xC05B
|
||||
SETAN2 = 0xC05C
|
||||
CLRAN2 = 0xC05D
|
||||
SETAN3 = 0xC05E
|
||||
CLRAN3 = 0xC05F
|
||||
mSETAN0 = 0xC058 // 4-bit annunciator inputs
|
||||
mCLRAN0 = 0xC059
|
||||
mSETAN1 = 0xC05A
|
||||
mCLRAN1 = 0xC05B
|
||||
mSETAN2 = 0xC05C
|
||||
mCLRAN2 = 0xC05D
|
||||
mSETAN3 = 0xC05E
|
||||
mCLRAN3 = 0xC05F
|
||||
|
||||
OPNAPPLE = 0xC061 // open apple (command) key data
|
||||
CLSAPPLE = 0xC062 // closed apple (option) key data
|
||||
STATEREG = 0xC068 // Has no effect on //e
|
||||
mOPNAPPLE = 0xC061 // open apple (command) key data
|
||||
mCLSAPPLE = 0xC062 // closed apple (option) key data
|
||||
mSTATEREG = 0xC068 // Has no effect on //e
|
||||
|
||||
PDLTRIG = 0xC070 // trigger paddles
|
||||
mPDLTRIG = 0xC070 // trigger paddles
|
||||
|
||||
// Slot 6 Drive IO
|
||||
S6CLRDRVP0 = 0xC0E0 // stepper phase 0 (Q0)
|
||||
S6SETDRVP0 = 0xC0E1 //
|
||||
S6CLRDRVP1 = 0xC0E2 // stepper phase 1 (Q1)
|
||||
S6SETDRVP1 = 0xC0E3 //
|
||||
S6CLRDRVP2 = 0xC0E4 // stepper phase 2 (Q2)
|
||||
S6SETDRVP2 = 0xC0E5 //
|
||||
S6CLRDRVP3 = 0xC0E6 // stepper phase 3 (Q3)
|
||||
S6SETDRVP3 = 0xC0E7 //
|
||||
S6MOTOROFF = 0xC0E8 // drive motor (Q4)
|
||||
S6MOTORON = 0xC0E9 //
|
||||
S6SELDRV1 = 0xC0EA // drive select (Q5)
|
||||
S6SELDRV2 = 0xC0EB //
|
||||
S6Q6L = 0xC0EC // read (Q6)
|
||||
S6Q6H = 0xC0ED // WP sense
|
||||
S6Q7L = 0xC0EE // WP sense/read (Q7)
|
||||
S6Q7H = 0xC0EF // write
|
||||
mS6CLRDRVP0 = 0xC0E0 // stepper phase 0 (Q0)
|
||||
mS6SETDRVP0 = 0xC0E1 //
|
||||
mS6CLRDRVP1 = 0xC0E2 // stepper phase 1 (Q1)
|
||||
mS6SETDRVP1 = 0xC0E3 //
|
||||
mS6CLRDRVP2 = 0xC0E4 // stepper phase 2 (Q2)
|
||||
mS6SETDRVP2 = 0xC0E5 //
|
||||
mS6CLRDRVP3 = 0xC0E6 // stepper phase 3 (Q3)
|
||||
mS6SETDRVP3 = 0xC0E7 //
|
||||
mS6MOTOROFF = 0xC0E8 // drive motor (Q4)
|
||||
mS6MOTORON = 0xC0E9 //
|
||||
mS6SELDRV1 = 0xC0EA // drive select (Q5)
|
||||
mS6SELDRV2 = 0xC0EB //
|
||||
mS6Q6L = 0xC0EC // read (Q6)
|
||||
mS6Q6H = 0xC0ED // WP sense
|
||||
mS6Q7L = 0xC0EE // WP sense/read (Q7)
|
||||
mS6Q7H = 0xC0EF // write
|
||||
)
|
||||
|
||||
// VideoState has 3 booleans which determine the video configuration:
|
||||
@ -106,6 +106,7 @@ var VideoState struct {
|
||||
Mixed bool
|
||||
}
|
||||
|
||||
// InitIO resets all IO states
|
||||
func InitIO() {
|
||||
// Empty slots that aren't yet implemented
|
||||
emptySlot(3)
|
||||
@ -138,81 +139,81 @@ func readWrite(address uint16, isRead bool) bool {
|
||||
}
|
||||
|
||||
switch address {
|
||||
case CLRAUXRD:
|
||||
case mCLRAUXRD:
|
||||
SetFakeAuxMemoryRead(false)
|
||||
return true
|
||||
case SETAUXRD:
|
||||
case mSETAUXRD:
|
||||
SetFakeAuxMemoryRead(true)
|
||||
return true
|
||||
|
||||
case CLRAUXWR:
|
||||
case mCLRAUXWR:
|
||||
SetFakeAuxMemoryWrite(false)
|
||||
return true
|
||||
case SETAUXWR:
|
||||
case mSETAUXWR:
|
||||
SetFakeAuxMemoryWrite(true)
|
||||
return true
|
||||
|
||||
case CLRAUXZP:
|
||||
case mCLRAUXZP:
|
||||
SetFakeAltZP(false)
|
||||
return true
|
||||
case SETAUXZP:
|
||||
case mSETAUXZP:
|
||||
SetFakeAltZP(true)
|
||||
return true
|
||||
|
||||
case CLR80VID:
|
||||
case mCLR80VID:
|
||||
SetCol80(false)
|
||||
return true
|
||||
case SET80VID:
|
||||
case mSET80VID:
|
||||
SetCol80(true)
|
||||
return true
|
||||
|
||||
case TXTPAGE1:
|
||||
case mTXTPAGE1:
|
||||
SetPage2(false)
|
||||
return true
|
||||
case TXTPAGE2:
|
||||
case mTXTPAGE2:
|
||||
SetPage2(true)
|
||||
return true
|
||||
|
||||
case CLRTEXT:
|
||||
case mCLRTEXT:
|
||||
VideoState.TextMode = false
|
||||
return true
|
||||
case SETTEXT:
|
||||
case mSETTEXT:
|
||||
VideoState.TextMode = true
|
||||
return true
|
||||
|
||||
case CLRMIXED:
|
||||
case mCLRMIXED:
|
||||
VideoState.Mixed = false
|
||||
return true
|
||||
case SETMIXED:
|
||||
case mSETMIXED:
|
||||
VideoState.Mixed = true
|
||||
return true
|
||||
|
||||
case CLRHIRES:
|
||||
case mCLRHIRES:
|
||||
VideoState.HiresMode = false
|
||||
return true
|
||||
case SETHIRES:
|
||||
case mSETHIRES:
|
||||
VideoState.HiresMode = true
|
||||
return true
|
||||
|
||||
case CLR80COL:
|
||||
case mCLR80COL:
|
||||
if !isRead {
|
||||
SetStore80(false)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case SET80COL:
|
||||
return false
|
||||
|
||||
case mSET80COL:
|
||||
SetStore80(true)
|
||||
return true
|
||||
|
||||
case STATEREG:
|
||||
case mSTATEREG:
|
||||
// Ignore not implemented memory management reg
|
||||
return true
|
||||
|
||||
// Drive stepper motor phase change
|
||||
case S6CLRDRVP0, S6SETDRVP0, S6CLRDRVP1, S6SETDRVP1, S6CLRDRVP2, S6SETDRVP2, S6CLRDRVP3, S6SETDRVP3:
|
||||
magnet := (address - S6CLRDRVP0) / 2
|
||||
on := ((address - S6CLRDRVP0) % 2) == 1
|
||||
case mS6CLRDRVP0, mS6SETDRVP0, mS6CLRDRVP1, mS6SETDRVP1, mS6CLRDRVP2, mS6SETDRVP2, mS6CLRDRVP3, mS6SETDRVP3:
|
||||
magnet := (address - mS6CLRDRVP0) / 2
|
||||
on := ((address - mS6CLRDRVP0) % 2) == 1
|
||||
if !on {
|
||||
// Turn off the magnet in Phases
|
||||
system.DriveState.Phases &= ^(1 << magnet)
|
||||
@ -225,10 +226,10 @@ func readWrite(address uint16, isRead bool) bool {
|
||||
// Move head if a neighboring magnet is on and all others are off
|
||||
direction := int8(0)
|
||||
if (system.DriveState.Phases & (1 << uint8((system.DriveState.Phase+1)&3))) != 0 {
|
||||
direction += 1
|
||||
direction++
|
||||
}
|
||||
if (system.DriveState.Phases & (1 << uint8((system.DriveState.Phase+3)&3))) != 0 {
|
||||
direction -= 1
|
||||
direction--
|
||||
}
|
||||
|
||||
// Move the head
|
||||
@ -251,37 +252,37 @@ func readWrite(address uint16, isRead bool) bool {
|
||||
|
||||
return true
|
||||
|
||||
case S6MOTOROFF:
|
||||
case mS6MOTOROFF:
|
||||
system.DriveState.Spinning = false
|
||||
return true
|
||||
case S6MOTORON:
|
||||
case mS6MOTORON:
|
||||
system.DriveState.Spinning = true
|
||||
return true
|
||||
|
||||
case S6SELDRV1:
|
||||
case mS6SELDRV1:
|
||||
system.DriveState.Drive = 1
|
||||
return true
|
||||
case S6SELDRV2:
|
||||
case mS6SELDRV2:
|
||||
system.DriveState.Drive = 2
|
||||
return true
|
||||
|
||||
case S6Q6L:
|
||||
case mS6Q6L:
|
||||
if !isRead {
|
||||
system.DriveState.Q6 = false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
case S6Q6H:
|
||||
case mS6Q6H:
|
||||
if isRead {
|
||||
system.DriveState.Q6 = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
case S6Q7L:
|
||||
case mS6Q7L:
|
||||
system.DriveState.Q7 = false
|
||||
return true
|
||||
case S6Q7H:
|
||||
case mS6Q7H:
|
||||
system.DriveState.Q7 = true
|
||||
return true
|
||||
|
||||
@ -299,64 +300,60 @@ func ReadIO(address uint16) uint8 {
|
||||
|
||||
switch address {
|
||||
|
||||
case KEYBOARD, STROBE:
|
||||
case mKEYBOARD, mSTROBE:
|
||||
keyBoardData, strobe := keyboard.Read()
|
||||
if address == KEYBOARD {
|
||||
if address == mKEYBOARD {
|
||||
return keyBoardData
|
||||
} else {
|
||||
keyboard.ResetStrobe()
|
||||
return strobe
|
||||
}
|
||||
keyboard.ResetStrobe()
|
||||
return strobe
|
||||
|
||||
case RDRAMRD, RDRAMWR, RDAUXZP:
|
||||
case mRDRAMRD, mRDRAMWR, mRDAUXZP:
|
||||
panic("Read/write aux memory not implemented")
|
||||
return 0x0d
|
||||
|
||||
case RDCXROM:
|
||||
case mRDCXROM:
|
||||
if UsingExternalSlotRom {
|
||||
return 0x8d
|
||||
} else {
|
||||
return 0x0d
|
||||
}
|
||||
return 0x0d
|
||||
|
||||
case RD80VID:
|
||||
case mRD80VID:
|
||||
// using 80-column display mode not implemented
|
||||
return 0x0d
|
||||
|
||||
case RDPAGE2:
|
||||
case mRDPAGE2:
|
||||
if Page2 {
|
||||
return 0x8d
|
||||
} else {
|
||||
return 0x0d
|
||||
}
|
||||
return 0x0d
|
||||
|
||||
// 4-bit annunciator inputs
|
||||
case SETAN0, CLRAN0, SETAN1, CLRAN1, SETAN2, CLRAN2, SETAN3, CLRAN3:
|
||||
case mSETAN0, mCLRAN0, mSETAN1, mCLRAN1, mSETAN2, mCLRAN2, mSETAN3, mCLRAN3:
|
||||
// Annunciators not implemented
|
||||
|
||||
case OPNAPPLE:
|
||||
case mOPNAPPLE:
|
||||
// Open apple key not implemented
|
||||
return 0
|
||||
|
||||
case CLSAPPLE:
|
||||
case mCLSAPPLE:
|
||||
// Closed apple key not implemented
|
||||
|
||||
case RD80COL:
|
||||
case mRD80COL:
|
||||
if Store80 {
|
||||
return 0x8d
|
||||
} else {
|
||||
return 0x0d
|
||||
}
|
||||
return 0x0d
|
||||
|
||||
case RDALTCH:
|
||||
case mRDALTCH:
|
||||
// RDALTCH not implemented, but it's also used, so don't fail on it.
|
||||
return 0x0d
|
||||
|
||||
case SPEAKER:
|
||||
case mSPEAKER:
|
||||
audio.Click()
|
||||
return 0
|
||||
|
||||
case S6Q6L:
|
||||
case mS6Q6L:
|
||||
// A read from disk
|
||||
return disk.ReadTrackData()
|
||||
|
||||
@ -367,7 +364,7 @@ func ReadIO(address uint16) uint8 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// ReadIO does a write in the $c000-$c0ff area
|
||||
// WriteIO does a write in the $c000-$c0ff area
|
||||
func WriteIO(address uint16, value uint8) {
|
||||
// Try the generic readWrite and return if it has handled the write
|
||||
if readWrite(address, false) {
|
||||
@ -376,29 +373,29 @@ func WriteIO(address uint16, value uint8) {
|
||||
|
||||
switch address {
|
||||
|
||||
case STROBE:
|
||||
case mSTROBE:
|
||||
keyboard.ResetStrobe()
|
||||
|
||||
case CLRCXROM:
|
||||
case mCLRCXROM:
|
||||
MapFirstHalfOfIO()
|
||||
case SETCXROM:
|
||||
case mSETCXROM:
|
||||
MapSecondHalfOfIO()
|
||||
|
||||
case CLRALTCH:
|
||||
case mCLRALTCH:
|
||||
return
|
||||
case SETALTCH:
|
||||
case mSETALTCH:
|
||||
panic("SETALTCH not implemented")
|
||||
|
||||
case CLR80COL:
|
||||
case mCLR80COL:
|
||||
// CLR80COL not implemented
|
||||
return
|
||||
|
||||
case CLRC3ROM:
|
||||
case mCLRC3ROM:
|
||||
// CLRC3ROM not implemented
|
||||
case SETC3ROM:
|
||||
case mSETC3ROM:
|
||||
// SETC3ROM not implemented
|
||||
|
||||
case S6Q6H:
|
||||
case mS6Q6H:
|
||||
// A write to disk
|
||||
disk.WriteTrackData(value)
|
||||
|
||||
|
67
mmu/mmu.go
67
mmu/mmu.go
@ -7,8 +7,11 @@ import (
|
||||
"github.com/freewilll/apple2/system"
|
||||
)
|
||||
|
||||
const RomPath = "apple2e.rom" // So far only one ROM is supported and it's loaded at startup
|
||||
const StackPage = 1 // The 6502 stack is at 0x100
|
||||
// RomPath is a hardcoded path to an Apple //e ROM file that's loaded at startup
|
||||
const RomPath = "apple2e.rom"
|
||||
|
||||
// StackPage is the location of the 6504 stack
|
||||
const StackPage = 1
|
||||
|
||||
// PhysicalMemory contains all the unmapped memory, ROM and RAM
|
||||
var PhysicalMemory struct {
|
||||
@ -18,8 +21,10 @@ var PhysicalMemory struct {
|
||||
RomC2 [0x1000]uint8 // Second half of IO ROM
|
||||
}
|
||||
|
||||
// Page tables for read & write
|
||||
// ReadPageTable is the page table for reads
|
||||
var ReadPageTable [0x100][]uint8
|
||||
|
||||
// WritePageTable is the page table for writes
|
||||
var WritePageTable [0x100][]uint8
|
||||
|
||||
// Memory mapping states
|
||||
@ -27,7 +32,7 @@ var (
|
||||
D000Bank int // one maps to $c000, two maps to $d000
|
||||
UsingExternalSlotRom bool // Which IO ROM is being used
|
||||
UpperReadMappedToROM bool // Do reads go to the RAM or ROM
|
||||
UpperRamReadOnly bool // Is the upper RAM read only
|
||||
UpperRAMReadOnly bool // Is the upper RAM read only
|
||||
FakeAuxMemoryRead bool // Aux memory isn't implemented
|
||||
FakeAuxMemoryWrite bool // Aux memory isn't implemented
|
||||
FakeAltZP bool // Aux memory isn't implemented
|
||||
@ -37,7 +42,7 @@ var (
|
||||
Page2 bool // Main memory Page2 is selected
|
||||
)
|
||||
|
||||
// Make page tables for current RAM, ROM and IO configuration
|
||||
// ApplyMemoryConfiguration creates the page tables for current RAM, ROM and IO configuration
|
||||
func ApplyMemoryConfiguration() {
|
||||
// Map main RAM for read/write
|
||||
for i := 0x0; i < 0xc0; i++ {
|
||||
@ -65,7 +70,7 @@ func ApplyMemoryConfiguration() {
|
||||
ReadPageTable[i] = PhysicalMemory.MainMemory[base : base+0x100]
|
||||
}
|
||||
|
||||
if UpperRamReadOnly {
|
||||
if UpperRAMReadOnly {
|
||||
WritePageTable[i] = nil
|
||||
} else {
|
||||
WritePageTable[i] = PhysicalMemory.MainMemory[base : base+0x100]
|
||||
@ -78,7 +83,7 @@ func ApplyMemoryConfiguration() {
|
||||
if !UpperReadMappedToROM {
|
||||
ReadPageTable[i] = PhysicalMemory.MainMemory[base : base+0x100]
|
||||
}
|
||||
if UpperRamReadOnly {
|
||||
if UpperRAMReadOnly {
|
||||
WritePageTable[i] = nil
|
||||
} else {
|
||||
WritePageTable[i] = PhysicalMemory.MainMemory[base : base+0x100]
|
||||
@ -93,13 +98,13 @@ func ApplyMemoryConfiguration() {
|
||||
|
||||
}
|
||||
|
||||
// Map 0xc100-0xcfff for reading from RomC1
|
||||
// MapFirstHalfOfIO maps 0xc100-0xcfff for reading from RomC1
|
||||
func MapFirstHalfOfIO() {
|
||||
UsingExternalSlotRom = false
|
||||
ApplyMemoryConfiguration()
|
||||
}
|
||||
|
||||
// Map 0xc100-0xcfff for reading from RomC2
|
||||
// MapSecondHalfOfIO map 0xc100-0xcfff for reading from RomC2
|
||||
func MapSecondHalfOfIO() {
|
||||
UsingExternalSlotRom = true
|
||||
ApplyMemoryConfiguration()
|
||||
@ -131,61 +136,66 @@ func loadApple2eROM() {
|
||||
}
|
||||
}
|
||||
|
||||
// InitApple2eROM loads the ROM and inits the ROM page tables
|
||||
func InitApple2eROM() {
|
||||
loadApple2eROM()
|
||||
MapFirstHalfOfIO() // Map 0xc100-0xcfff for reading
|
||||
InitROM() // Map 0xd000-0xffff for reading
|
||||
}
|
||||
|
||||
// Set upper memory area for reading from ROM
|
||||
// InitROM sets the upper memory area for reading from ROM
|
||||
func InitROM() {
|
||||
UpperReadMappedToROM = true
|
||||
ApplyMemoryConfiguration()
|
||||
}
|
||||
|
||||
// SetUpperReadMappedToROM sets the upper area so that reads are done from the ROM if true or RAM if false
|
||||
func SetUpperReadMappedToROM(value bool) {
|
||||
UpperReadMappedToROM = value
|
||||
ApplyMemoryConfiguration()
|
||||
}
|
||||
|
||||
func SetUpperRamReadOnly(value bool) {
|
||||
UpperRamReadOnly = value
|
||||
// SetUpperRAMReadOnly sets the upper RAM area to read only
|
||||
func SetUpperRAMReadOnly(value bool) {
|
||||
UpperRAMReadOnly = value
|
||||
ApplyMemoryConfiguration()
|
||||
}
|
||||
|
||||
// Set d000 bank to map to $c000 or $d000 in the physical memory
|
||||
// SetD000Bank sets the $d000 bank to map to $c000 or $d000 in the physical memory
|
||||
func SetD000Bank(value int) {
|
||||
D000Bank = value
|
||||
ApplyMemoryConfiguration()
|
||||
}
|
||||
|
||||
// Aux memory hasn't been implemented. If aux memory is selected, and a read
|
||||
// SetFakeAuxMemoryRead sets an internal state to fake aux memory reads. Aux memory hasn't been implemented. If aux memory is selected, and a read
|
||||
// is attempted, then nonsense must be returned.
|
||||
func SetFakeAuxMemoryRead(value bool) {
|
||||
FakeAuxMemoryRead = value
|
||||
ApplyMemoryConfiguration()
|
||||
}
|
||||
|
||||
// Aux memory hasn't been implemented. If aux memory is selected, and a write
|
||||
// SetFakeAuxMemoryWrite sets an internal state to fake aux memory writes. Aux memory hasn't been implemented. If aux memory is selected, and a write
|
||||
// is attempted, then it must be ignored.
|
||||
func SetFakeAuxMemoryWrite(value bool) {
|
||||
FakeAuxMemoryWrite = value
|
||||
ApplyMemoryConfiguration()
|
||||
}
|
||||
|
||||
// Alternate zero page isn't implemented
|
||||
// SetFakeAltZP sets an internal state to fake a missing alternate zero page. Alternate zero page isn't implemented
|
||||
func SetFakeAltZP(value bool) {
|
||||
FakeAltZP = value
|
||||
ApplyMemoryConfiguration()
|
||||
}
|
||||
|
||||
// 80 column card isn't implemented
|
||||
// SetCol80 sets an internal state to fake a missing 80 column card
|
||||
func SetCol80(value bool) {
|
||||
Col80 = value
|
||||
// No changes are needed when this is toggled
|
||||
}
|
||||
|
||||
// Page switching is only implemented for the main memory
|
||||
// SetPage2 sets page1/page2 in text, lores or hires. This only works if the
|
||||
// 80 column card is disabled since the 80 columns card has not yet nbeen
|
||||
// implemented.
|
||||
func SetPage2(value bool) {
|
||||
// If the 80 column card is enabled, then this toggles aux memory
|
||||
// Otherwise, page1/page2 is toggled in the main memory
|
||||
@ -197,7 +207,7 @@ func SetPage2(value bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// 80 column card isn't implemented
|
||||
// SetStore80 sets an internal state to fake a missing 80 column card
|
||||
func SetStore80(value bool) {
|
||||
Store80 = value
|
||||
FakePage2 = value
|
||||
@ -206,7 +216,7 @@ func SetStore80(value bool) {
|
||||
|
||||
// InitRAM sets all default RAM memory settings and resets the page tables
|
||||
func InitRAM() {
|
||||
UpperRamReadOnly = false
|
||||
UpperRAMReadOnly = false
|
||||
D000Bank = 2
|
||||
FakeAuxMemoryRead = false // Aux memory isn't implemented
|
||||
FakeAuxMemoryWrite = false // Aux memory isn't implemented
|
||||
@ -217,22 +227,23 @@ func InitRAM() {
|
||||
ApplyMemoryConfiguration()
|
||||
}
|
||||
|
||||
// WipeRAM wipes all the physical RAM
|
||||
func WipeRAM() {
|
||||
for i := 0; i < 0x10000; i++ {
|
||||
PhysicalMemory.MainMemory[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// SetMemoryMode is used to set UpperRamReadOnly, UpperReadMappedToROM and D000Bank number
|
||||
// SetMemoryMode is used to set UpperRAMReadOnly, UpperReadMappedToROM and D000Bank number
|
||||
func SetMemoryMode(mode uint8) {
|
||||
// mode corresponds to a read/write to $c080 with
|
||||
// $c080 mode=$00
|
||||
// $c08f mode=$0f
|
||||
|
||||
if (mode & 1) == 0 {
|
||||
UpperRamReadOnly = true
|
||||
UpperRAMReadOnly = true
|
||||
} else {
|
||||
UpperRamReadOnly = false
|
||||
UpperRAMReadOnly = false
|
||||
}
|
||||
|
||||
if (((mode & 2) >> 1) ^ (mode & 1)) == 0 {
|
||||
@ -266,18 +277,18 @@ func ReadMemory(address uint16) uint8 {
|
||||
if address >= 0x200 {
|
||||
// Return nothingness
|
||||
return uint8(0x00)
|
||||
} else {
|
||||
if FakeAltZP {
|
||||
return uint8(0x00)
|
||||
}
|
||||
}
|
||||
if FakeAltZP {
|
||||
return uint8(0x00)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Implicit else, we're reading the main non-IO RAM
|
||||
return ReadPageTable[address>>8][address&0xff]
|
||||
}
|
||||
|
||||
// ReadMemory writes to the ROM or RAM page table
|
||||
// WriteMemory writes to the ROM or RAM page table
|
||||
func WriteMemory(address uint16, value uint8) {
|
||||
if (address >= 0xc000) && (address < 0xc100) {
|
||||
WriteIO(address, value)
|
||||
|
@ -4,22 +4,46 @@ package system
|
||||
// the packages.
|
||||
|
||||
const (
|
||||
CpuFrequency = 1023000 // 6402 CPU frequency in Hz
|
||||
AudioSampleRate = 44100 // Audio sample rate in Hz
|
||||
// CPUFrequency is the 6502 CPU frequency in Hz
|
||||
CPUFrequency = 1023000
|
||||
|
||||
// AudioSampleRate is the audio sample rate in Hz
|
||||
AudioSampleRate = 44100
|
||||
)
|
||||
|
||||
var (
|
||||
PendingInterrupt bool // Set when an interrupt has just happened
|
||||
PendingNMI bool // Set when a non maskable interrupt has just happened
|
||||
RunningTests bool // For testing
|
||||
RunningFunctionalTests bool // For testing
|
||||
RunningInterruptTests bool // For testing
|
||||
Cycles uint64 // Total CPU cycles executed
|
||||
FrameCycles uint64 // CPU cycles executed in the current frame
|
||||
AudioChannel chan int16 // Audio channel
|
||||
LastAudioValue int16 // + or - value of the current square wave
|
||||
LastAudioCycles uint64 // Last CPU cycle when audio was sent to the channel
|
||||
AudioAttenuationCounter uint64 // Counter to keep track of when the audio should be zeroed after inactivity
|
||||
// PendingInterrupt is set when an interrupt has just happened
|
||||
PendingInterrupt bool
|
||||
|
||||
// PendingNMI is set when a non maskable interrupt has just happened
|
||||
PendingNMI bool
|
||||
|
||||
// RunningTests is set when tests are running
|
||||
RunningTests bool
|
||||
|
||||
// RunningFunctionalTests is set when functional tests are running
|
||||
RunningFunctionalTests bool
|
||||
|
||||
// RunningInterruptTests is set when interupt tests are running
|
||||
RunningInterruptTests bool
|
||||
|
||||
// Cycles is the total CPU cycles executed
|
||||
Cycles uint64
|
||||
|
||||
// FrameCycles is the CPU cycles executed in the current frame
|
||||
FrameCycles uint64
|
||||
|
||||
// AudioChannel is the audio channel used to produce and consume audio samples between the cpu and audio packages
|
||||
AudioChannel chan int16
|
||||
|
||||
// LastAudioValue is the + or - value of the current square wave
|
||||
LastAudioValue int16
|
||||
|
||||
// LastAudioCycles is the ast CPU cycle when audio was sent to the channel
|
||||
LastAudioCycles uint64
|
||||
|
||||
// AudioAttenuationCounter is a counter to keep track of when the audio should be zeroed after inactivity
|
||||
AudioAttenuationCounter uint64
|
||||
)
|
||||
|
||||
// DriveState has the state of the disk drive
|
||||
@ -40,7 +64,7 @@ func Init() {
|
||||
LastAudioValue = 0x2000
|
||||
}
|
||||
|
||||
// Handle a write to a magic test address that triggers an interrupt and/or an NMI
|
||||
// WriteInterruptTestOpenCollector handles a write to a magic test address that triggers an interrupt and/or an NMI
|
||||
func WriteInterruptTestOpenCollector(address uint16, oldValue uint8, value uint8) {
|
||||
oldInterrupt := (oldValue & 0x1) == 0x1
|
||||
oldNMI := (oldValue & 0x2) == 0x2
|
||||
|
@ -60,7 +60,7 @@ func RunUntilBreakPoint(t *testing.T, breakAddress uint16, seconds int, showInst
|
||||
system.LastAudioCycles = 0
|
||||
exitAtBreak := false
|
||||
disableFirmwareWait := false
|
||||
cpu.Run(showInstructions, &breakAddress, exitAtBreak, disableFirmwareWait, uint64(system.CpuFrequency*seconds))
|
||||
cpu.Run(showInstructions, &breakAddress, exitAtBreak, disableFirmwareWait, uint64(system.CPUFrequency*seconds))
|
||||
if cpu.State.PC != breakAddress {
|
||||
t.Fatalf("Did not reach breakpoint at %04x. Got to %04x", breakAddress, cpu.State.PC)
|
||||
}
|
||||
|
@ -12,9 +12,11 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
ScreenSizeFactor = 1 // Factor by which the whole screen is resized
|
||||
textVideoMemory = 0x400 // Base location of page 1 text video memory
|
||||
flashFrames = 11 // Number of frames when FLASH mode is toggled
|
||||
// ScreenSizeFactor is the fFactor by which the whole screen is resized
|
||||
ScreenSizeFactor = 1
|
||||
|
||||
textVideoMemory = 0x400 // Base location of page 1 text video memory
|
||||
flashFrames = 11 // Number of frames when FLASH mode is toggled
|
||||
)
|
||||
|
||||
// drawTextLoresByte is a function definition used for mixed text/lores rendering
|
||||
@ -25,7 +27,9 @@ var (
|
||||
flashCounter int // Counter used for flashing characters on the text screen
|
||||
flashOn bool // Are we currently flashing?
|
||||
loresSquares [16]*ebiten.Image // Colored blocks for lores rendering
|
||||
ShowFPS bool // Show FPS in corner?
|
||||
|
||||
// ShowFPS determines if the FPS is shown in the corner of the video
|
||||
ShowFPS bool
|
||||
)
|
||||
|
||||
// initTextCharMap initializes the text character map
|
||||
@ -130,7 +134,7 @@ func drawText(screen *ebiten.Image, x int, y int, value uint8) error {
|
||||
// drawLores draws two colored lores squares at the equivalent text location x,y.
|
||||
func drawLores(screen *ebiten.Image, x int, y int, value uint8) error {
|
||||
// Convert the 8 bit value to two 4 bit values
|
||||
var values [2]uint8 = [2]uint8{value & 0xf, value >> 4}
|
||||
var values = [2]uint8{value & 0xf, value >> 4}
|
||||
|
||||
// Render top & bottom squares
|
||||
for i := 0; i < 2; i++ {
|
||||
|
Loading…
x
Reference in New Issue
Block a user