mirror of
https://github.com/freewilll/apple2-go.git
synced 2024-06-08 06:29:28 +00:00
Added very basic audio processing
Every frame sends a bunch of audio samples in a channel. This channel is consumed by the audio Read() function which is called asynchronously. There's all kinds of timing issues where the audio/video are not aligned. Issues: - There's a large delay between the audio being produced and it being played - Something with the timing is wrong. The first not of lemonade stand and the system beep are both incorrect. Changing the CPU frequency fixes it for one but not for the other. This means something must be wrong in the cycle counting. Also added FPS display that can be toggled with ctrl-alt-F.
This commit is contained in:
parent
23f7dc04b2
commit
8284073beb
108
audio/audio.go
Normal file
108
audio/audio.go
Normal file
|
@ -0,0 +1,108 @@
|
|||
package audio
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"mos6502go/system"
|
||||
|
||||
ebiten_audio "github.com/hajimehoshi/ebiten/audio"
|
||||
)
|
||||
|
||||
var (
|
||||
audioContext *ebiten_audio.Context
|
||||
player *ebiten_audio.Player
|
||||
firstAudio bool
|
||||
Mute bool
|
||||
)
|
||||
|
||||
type stream struct{}
|
||||
|
||||
func (s *stream) Read(data []byte) (int, error) {
|
||||
dataLen := len(data)
|
||||
|
||||
if firstAudio {
|
||||
// The first time, drain the audio queue
|
||||
firstAudio = false
|
||||
|
||||
for i := 0; i < len(system.AudioChannel); i++ {
|
||||
<-system.AudioChannel
|
||||
}
|
||||
return dataLen, nil
|
||||
}
|
||||
|
||||
if dataLen%4 != 0 {
|
||||
return 0, errors.New("dataLen % 4 must be 0")
|
||||
}
|
||||
|
||||
if Mute {
|
||||
return dataLen, nil
|
||||
}
|
||||
|
||||
samples := dataLen / 4
|
||||
|
||||
for i := 0; i < dataLen; i++ {
|
||||
data[i] = 0
|
||||
}
|
||||
|
||||
for i := 0; i < samples; i++ {
|
||||
b := <-system.AudioChannel
|
||||
|
||||
data[4*i] = byte(b)
|
||||
data[4*i+1] = byte(b >> 8)
|
||||
data[4*i+2] = byte(b)
|
||||
data[4*i+3] = byte(b >> 8)
|
||||
}
|
||||
|
||||
return dataLen, nil
|
||||
}
|
||||
|
||||
func (s *stream) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func Init() {
|
||||
system.AudioCycles = 0
|
||||
firstAudio = true
|
||||
Mute = false
|
||||
|
||||
var err error
|
||||
audioContext, err = ebiten_audio.NewContext(system.AudioSampleRate)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pass the (infinite) stream to audio.NewPlayer.
|
||||
// After calling Play, the stream never ends as long as the player object lives.
|
||||
// var err error
|
||||
player, err = ebiten_audio.NewPlayer(audioContext, &stream{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
player.Play()
|
||||
}
|
||||
|
||||
func Click() {
|
||||
ForwardToFrameCycle()
|
||||
system.AudioAttenuationCounter = 4000
|
||||
system.LastAudioValue = ^system.LastAudioValue
|
||||
}
|
||||
|
||||
func attenuate(sample uint16) uint16 {
|
||||
if system.AudioAttenuationCounter == 0 {
|
||||
return 0
|
||||
} else {
|
||||
system.AudioAttenuationCounter--
|
||||
return sample
|
||||
}
|
||||
}
|
||||
|
||||
func ForwardToFrameCycle() {
|
||||
ratio := float64(system.AudioSampleRate) / system.CpuFrequency
|
||||
|
||||
samples := uint64(ratio * float64(system.FrameCycles-system.LastAudioCycles))
|
||||
var i uint64
|
||||
for i = 0; i < samples; i++ {
|
||||
b := attenuate(system.LastAudioValue)
|
||||
system.AudioChannel <- b
|
||||
}
|
||||
system.LastAudioCycles = system.FrameCycles
|
||||
}
|
|
@ -2,12 +2,15 @@ package main
|
|||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
|
||||
"mos6502go/audio"
|
||||
"mos6502go/cpu"
|
||||
"mos6502go/keyboard"
|
||||
"mos6502go/mmu"
|
||||
"mos6502go/system"
|
||||
"mos6502go/utils"
|
||||
"mos6502go/video"
|
||||
)
|
||||
|
@ -21,6 +24,7 @@ const (
|
|||
var showInstructions *bool
|
||||
var disableFirmwareWait *bool
|
||||
var resetKeysDown bool
|
||||
var fpsKeysDown bool
|
||||
var breakAddress *uint16
|
||||
|
||||
func reset() {
|
||||
|
@ -30,23 +34,43 @@ func reset() {
|
|||
cpu.State.PC = uint16(lsb) + uint16(msb)<<8
|
||||
}
|
||||
|
||||
// checkResetKeys check ctrl-alt-R has been pressed. Releasing the R does a warm reset
|
||||
func checkResetKeys() {
|
||||
// checkSpecialKeys checks
|
||||
// - ctrl-alt-R has been pressed. Releasing the R does a warm reset
|
||||
// - ctrl-alt-F has been pressed, toggling FPS display
|
||||
func checkSpecialKeys() {
|
||||
if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyAlt) && ebiten.IsKeyPressed(ebiten.KeyR) {
|
||||
resetKeysDown = true
|
||||
} else if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyAlt) && !ebiten.IsKeyPressed(ebiten.KeyR) && resetKeysDown {
|
||||
resetKeysDown = false
|
||||
reset()
|
||||
|
||||
} else {
|
||||
resetKeysDown = false
|
||||
}
|
||||
|
||||
if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyAlt) && ebiten.IsKeyPressed(ebiten.KeyF) {
|
||||
fpsKeysDown = true
|
||||
} else if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyAlt) && !ebiten.IsKeyPressed(ebiten.KeyF) && fpsKeysDown {
|
||||
fpsKeysDown = false
|
||||
video.ShowFPS = !video.ShowFPS
|
||||
fmt.Println("Toggled")
|
||||
|
||||
} else {
|
||||
fpsKeysDown = false
|
||||
}
|
||||
}
|
||||
|
||||
func update(screen *ebiten.Image) error {
|
||||
keyboard.Poll()
|
||||
checkResetKeys()
|
||||
checkSpecialKeys()
|
||||
|
||||
system.FrameCycles = 0
|
||||
system.LastAudioCycles = 0
|
||||
cpu.Run(*showInstructions, breakAddress, *disableFirmwareWait, system.CpuFrequency/60)
|
||||
audio.ForwardToFrameCycle()
|
||||
system.Cycles += system.FrameCycles
|
||||
system.FrameCycles = 0
|
||||
|
||||
cpu.Run(*showInstructions, breakAddress, *disableFirmwareWait, 1024000/60)
|
||||
return video.DrawScreen(screen)
|
||||
}
|
||||
|
||||
|
@ -54,6 +78,7 @@ func main() {
|
|||
showInstructions = flag.Bool("show-instructions", false, "Show instructions code while running")
|
||||
disableFirmwareWait = flag.Bool("disable-wait", false, "Ignore JSRs to firmware wait at $FCA8")
|
||||
breakAddressString := flag.String("break", "", "Break on address")
|
||||
mute := flag.Bool("mute", false, "Mute sound")
|
||||
diskImage := flag.String("image", "", "Disk Image")
|
||||
flag.Parse()
|
||||
|
||||
|
@ -74,6 +99,9 @@ func main() {
|
|||
|
||||
keyboard.Init()
|
||||
video.Init()
|
||||
audio.Init()
|
||||
audio.Mute = *mute
|
||||
system.Init()
|
||||
|
||||
reset()
|
||||
|
||||
|
|
252
cpu/cpu.go
252
cpu/cpu.go
|
@ -120,7 +120,7 @@ func pop16() uint16 {
|
|||
return lsb + msb<<8
|
||||
}
|
||||
|
||||
func branch(cycles *int, instructionName string, doBranch bool) {
|
||||
func branch(instructionName string, doBranch bool) {
|
||||
value := mmu.ReadMemory(State.PC + 1)
|
||||
|
||||
var relativeAddress uint16
|
||||
|
@ -130,7 +130,7 @@ func branch(cycles *int, instructionName string, doBranch bool) {
|
|||
relativeAddress = State.PC + uint16(value) + 2 - 0x100
|
||||
}
|
||||
|
||||
*cycles += 2
|
||||
system.FrameCycles += 2
|
||||
if doBranch {
|
||||
if system.RunningTests && State.PC == relativeAddress {
|
||||
fmt.Printf("Trap at $%04x\n", relativeAddress)
|
||||
|
@ -139,9 +139,9 @@ func branch(cycles *int, instructionName string, doBranch bool) {
|
|||
|
||||
samePage := (State.PC & 0xff00) != (relativeAddress & 0xff00)
|
||||
if samePage {
|
||||
*cycles += 1
|
||||
system.FrameCycles += 1
|
||||
} else {
|
||||
*cycles += 2
|
||||
system.FrameCycles += 2
|
||||
}
|
||||
State.PC = relativeAddress
|
||||
} else {
|
||||
|
@ -238,117 +238,117 @@ func readMemoryWithAddressMode(addressMode byte) (result uint8, pageBoundaryCros
|
|||
}
|
||||
|
||||
// STA, STX and STY
|
||||
func store(cycles *int, regValue uint8, addressMode byte) {
|
||||
func store(regValue uint8, addressMode byte) {
|
||||
address, _ := getAddressFromAddressMode(addressMode)
|
||||
mmu.WriteMemory(address, regValue)
|
||||
|
||||
switch addressMode {
|
||||
case AmZeroPage:
|
||||
State.PC += 2
|
||||
*cycles += 3
|
||||
system.FrameCycles += 3
|
||||
case AmZeroPageX:
|
||||
State.PC += 2
|
||||
*cycles += 4
|
||||
system.FrameCycles += 4
|
||||
case AmZeroPageY:
|
||||
State.PC += 2
|
||||
*cycles += 4
|
||||
system.FrameCycles += 4
|
||||
case AmAbsolute:
|
||||
State.PC += 3
|
||||
*cycles += 4
|
||||
system.FrameCycles += 4
|
||||
case AmAbsoluteX:
|
||||
State.PC += 3
|
||||
*cycles += 5
|
||||
system.FrameCycles += 5
|
||||
case AmAbsoluteY:
|
||||
State.PC += 3
|
||||
*cycles += 5
|
||||
system.FrameCycles += 5
|
||||
case AmIndirect:
|
||||
State.PC += 2
|
||||
*cycles += 6
|
||||
system.FrameCycles += 6
|
||||
case AmIndirectX:
|
||||
State.PC += 2
|
||||
*cycles += 6
|
||||
system.FrameCycles += 6
|
||||
case AmIndirectY:
|
||||
State.PC += 2
|
||||
*cycles += 6
|
||||
system.FrameCycles += 6
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown address mode %d in store()", addressMode))
|
||||
}
|
||||
}
|
||||
|
||||
// These instructions take the same amount of cycles
|
||||
func advanceCyclesForAcculumatorOperation(cycles *int, addressMode byte, pageBoundaryCrossed bool) {
|
||||
extraCycle := 0
|
||||
// These instructions take the same amount of system.FrameCycles
|
||||
func advanceCyclesForAcculumatorOperation(addressMode byte, pageBoundaryCrossed bool) {
|
||||
extraCycle := uint64(0)
|
||||
if pageBoundaryCrossed {
|
||||
extraCycle = 1
|
||||
}
|
||||
|
||||
switch addressMode {
|
||||
case AmImmediate:
|
||||
*cycles += 2
|
||||
system.FrameCycles += 2
|
||||
case AmZeroPage:
|
||||
*cycles += 3
|
||||
system.FrameCycles += 3
|
||||
case AmZeroPageX:
|
||||
*cycles += 4
|
||||
system.FrameCycles += 4
|
||||
case AmZeroPageY:
|
||||
*cycles += 4
|
||||
system.FrameCycles += 4
|
||||
case AmAbsolute:
|
||||
*cycles += 4
|
||||
system.FrameCycles += 4
|
||||
case AmAbsoluteX:
|
||||
*cycles += 4 + extraCycle
|
||||
system.FrameCycles += 4 + extraCycle
|
||||
case AmAbsoluteY:
|
||||
*cycles += 4 + extraCycle
|
||||
system.FrameCycles += 4 + extraCycle
|
||||
case AmIndirectX:
|
||||
*cycles += 6
|
||||
system.FrameCycles += 6
|
||||
case AmIndirectY:
|
||||
*cycles += 5 + extraCycle
|
||||
system.FrameCycles += 5 + extraCycle
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown address mode %d in advanceCyclesForAcculumatorOperation()", addressMode))
|
||||
}
|
||||
}
|
||||
|
||||
func load(cycles *int, addressMode byte) uint8 {
|
||||
func load(addressMode byte) uint8 {
|
||||
value, pageBoundaryCrossed := readMemoryWithAddressMode(addressMode)
|
||||
setN(value)
|
||||
setZ(value)
|
||||
advanceCyclesForAcculumatorOperation(cycles, addressMode, pageBoundaryCrossed)
|
||||
advanceCyclesForAcculumatorOperation(addressMode, pageBoundaryCrossed)
|
||||
return value
|
||||
}
|
||||
|
||||
func cmp(cycles *int, regValue uint8, addressMode byte) {
|
||||
func cmp(regValue uint8, addressMode byte) {
|
||||
value, pageBoundaryCrossed := readMemoryWithAddressMode(addressMode)
|
||||
var result uint16
|
||||
result = uint16(regValue) - uint16(value)
|
||||
setC(result < 0x100)
|
||||
setN(uint8(result))
|
||||
setZ(uint8(result & 0xff))
|
||||
advanceCyclesForAcculumatorOperation(cycles, addressMode, pageBoundaryCrossed)
|
||||
advanceCyclesForAcculumatorOperation(addressMode, pageBoundaryCrossed)
|
||||
}
|
||||
|
||||
func ora(cycles *int, addressMode byte) {
|
||||
func ora(addressMode byte) {
|
||||
value, pageBoundaryCrossed := readMemoryWithAddressMode(addressMode)
|
||||
State.A |= value
|
||||
setN(State.A)
|
||||
setZ(State.A)
|
||||
advanceCyclesForAcculumatorOperation(cycles, addressMode, pageBoundaryCrossed)
|
||||
advanceCyclesForAcculumatorOperation(addressMode, pageBoundaryCrossed)
|
||||
}
|
||||
|
||||
func and(cycles *int, addressMode byte) {
|
||||
func and(addressMode byte) {
|
||||
value, pageBoundaryCrossed := readMemoryWithAddressMode(addressMode)
|
||||
State.A &= value
|
||||
setN(State.A)
|
||||
setZ(State.A)
|
||||
advanceCyclesForAcculumatorOperation(cycles, addressMode, pageBoundaryCrossed)
|
||||
advanceCyclesForAcculumatorOperation(addressMode, pageBoundaryCrossed)
|
||||
}
|
||||
|
||||
func eor(cycles *int, addressMode byte) {
|
||||
func eor(addressMode byte) {
|
||||
value, pageBoundaryCrossed := readMemoryWithAddressMode(addressMode)
|
||||
State.A ^= value
|
||||
setN(State.A)
|
||||
setZ(State.A)
|
||||
advanceCyclesForAcculumatorOperation(cycles, addressMode, pageBoundaryCrossed)
|
||||
advanceCyclesForAcculumatorOperation(addressMode, pageBoundaryCrossed)
|
||||
}
|
||||
|
||||
func adc(cycles *int, addressMode byte) {
|
||||
func adc(addressMode byte) {
|
||||
value, pageBoundaryCrossed := readMemoryWithAddressMode(addressMode)
|
||||
|
||||
var temp uint16
|
||||
|
@ -388,10 +388,10 @@ func adc(cycles *int, addressMode byte) {
|
|||
|
||||
setN(State.A)
|
||||
setZ(State.A)
|
||||
advanceCyclesForAcculumatorOperation(cycles, addressMode, pageBoundaryCrossed)
|
||||
advanceCyclesForAcculumatorOperation(addressMode, pageBoundaryCrossed)
|
||||
}
|
||||
|
||||
func sbc(cycles *int, addressMode byte) {
|
||||
func sbc(addressMode byte) {
|
||||
value, pageBoundaryCrossed := readMemoryWithAddressMode(addressMode)
|
||||
|
||||
var temp uint16
|
||||
|
@ -427,7 +427,7 @@ func sbc(cycles *int, addressMode byte) {
|
|||
|
||||
setC(temp < 0x100)
|
||||
State.A = uint8(temp & 0xff)
|
||||
advanceCyclesForAcculumatorOperation(cycles, addressMode, pageBoundaryCrossed)
|
||||
advanceCyclesForAcculumatorOperation(addressMode, pageBoundaryCrossed)
|
||||
}
|
||||
|
||||
func bit(address uint16) {
|
||||
|
@ -438,7 +438,7 @@ func bit(address uint16) {
|
|||
}
|
||||
|
||||
// Read the address/value for an ASL, LSR, ROR, ROL
|
||||
func preProcessShift(cycles *int, addressMode byte) (address uint16, value uint8) {
|
||||
func preProcessShift(addressMode byte) (address uint16, value uint8) {
|
||||
if addressMode == AmAccumulator {
|
||||
value = State.A
|
||||
} else {
|
||||
|
@ -456,85 +456,85 @@ func preProcessShift(cycles *int, addressMode byte) (address uint16, value uint8
|
|||
return
|
||||
}
|
||||
|
||||
// Store the result of a ASL, LSR, ROR, ROL and advance PC and cycles
|
||||
func postProcessShift(cycles *int, addressMode byte, address uint16, value uint8) {
|
||||
// Store the result of a ASL, LSR, ROR, ROL and advance PC and system.FrameCycles
|
||||
func postProcessShift(addressMode byte, address uint16, value uint8) {
|
||||
switch addressMode {
|
||||
case AmAccumulator:
|
||||
State.A = value
|
||||
State.PC += 1
|
||||
*cycles += 2
|
||||
system.FrameCycles += 2
|
||||
case AmZeroPage:
|
||||
mmu.WriteMemory(address, value)
|
||||
State.PC += 2
|
||||
*cycles += 5
|
||||
system.FrameCycles += 5
|
||||
case AmZeroPageX:
|
||||
mmu.WriteMemory(address, value)
|
||||
State.PC += 2
|
||||
*cycles += 6
|
||||
system.FrameCycles += 6
|
||||
case AmAbsolute:
|
||||
mmu.WriteMemory(address, value)
|
||||
State.PC += 3
|
||||
*cycles += 6
|
||||
system.FrameCycles += 6
|
||||
case AmAbsoluteX:
|
||||
mmu.WriteMemory(address, value)
|
||||
State.PC += 3
|
||||
*cycles += 7
|
||||
system.FrameCycles += 7
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown address mode %d in postProcessShift()", addressMode))
|
||||
}
|
||||
}
|
||||
|
||||
func postProcessIncDec(cycles *int, addressMode byte) {
|
||||
func postProcessIncDec(addressMode byte) {
|
||||
switch addressMode {
|
||||
case AmZeroPage:
|
||||
State.PC += 2
|
||||
*cycles += 5
|
||||
system.FrameCycles += 5
|
||||
case AmZeroPageX:
|
||||
State.PC += 2
|
||||
*cycles += 6
|
||||
system.FrameCycles += 6
|
||||
case AmAbsolute:
|
||||
State.PC += 3
|
||||
*cycles += 6
|
||||
system.FrameCycles += 6
|
||||
case AmAbsoluteX:
|
||||
State.PC += 3
|
||||
*cycles += 7
|
||||
system.FrameCycles += 7
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown address mode %d in INC", addressMode))
|
||||
}
|
||||
}
|
||||
|
||||
func brk(cycles *int) {
|
||||
func brk() {
|
||||
push16(State.PC + 2)
|
||||
State.P |= CpuFlagB
|
||||
push8(State.P)
|
||||
State.P |= CpuFlagI
|
||||
State.PC = uint16(mmu.ReadMemory(0xffff))<<8 + uint16(mmu.ReadMemory(0xfffe))
|
||||
*cycles += 7
|
||||
system.FrameCycles += 7
|
||||
}
|
||||
|
||||
func irq(cycles *int) {
|
||||
func irq() {
|
||||
push16(State.PC)
|
||||
State.P &= ^CpuFlagB
|
||||
push8(State.P)
|
||||
State.P |= CpuFlagI
|
||||
State.PC = uint16(mmu.ReadMemory(0xffff))<<8 + uint16(mmu.ReadMemory(0xfffe))
|
||||
*cycles += 7
|
||||
system.FrameCycles += 7
|
||||
}
|
||||
|
||||
func nmi(cycles *int) {
|
||||
func nmi() {
|
||||
push16(State.PC)
|
||||
State.P &= ^CpuFlagB
|
||||
push8(State.P)
|
||||
State.P |= CpuFlagI
|
||||
State.PC = uint16(mmu.ReadMemory(0xfffb))<<8 + uint16(mmu.ReadMemory(0xfffa))
|
||||
*cycles += 7
|
||||
system.FrameCycles += 7
|
||||
}
|
||||
|
||||
func Run(showInstructions bool, breakAddress *uint16, disableFirmwareWait bool, wantedCycles int) {
|
||||
cycles := 0
|
||||
func Run(showInstructions bool, breakAddress *uint16, disableFirmwareWait bool, wantedCycles uint64) {
|
||||
system.FrameCycles = 0
|
||||
|
||||
for {
|
||||
if (wantedCycles != 0) && (cycles >= wantedCycles) {
|
||||
if (wantedCycles != 0) && (system.FrameCycles >= wantedCycles) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -549,13 +549,13 @@ func Run(showInstructions bool, breakAddress *uint16, disableFirmwareWait bool,
|
|||
}
|
||||
|
||||
if system.PendingInterrupt && ((State.P & CpuFlagI) == 0) {
|
||||
irq(&cycles)
|
||||
irq()
|
||||
system.PendingInterrupt = false
|
||||
continue
|
||||
}
|
||||
|
||||
if system.PendingNMI {
|
||||
nmi(&cycles)
|
||||
nmi()
|
||||
system.PendingNMI = false
|
||||
continue
|
||||
}
|
||||
|
@ -582,15 +582,15 @@ func Run(showInstructions bool, breakAddress *uint16, disableFirmwareWait bool,
|
|||
os.Exit(0)
|
||||
}
|
||||
State.PC = value
|
||||
cycles += 3
|
||||
system.FrameCycles += 3
|
||||
case 0x6c: // JMP ($0000)
|
||||
value := uint16(mmu.ReadMemory(State.PC+1)) + uint16(mmu.ReadMemory(State.PC+2))<<8
|
||||
State.PC = uint16(mmu.ReadMemory(value)) + uint16(mmu.ReadMemory(value+1))<<8
|
||||
cycles += 5
|
||||
system.FrameCycles += 5
|
||||
|
||||
case 0x20: // JSR $0000
|
||||
value := uint16(mmu.ReadMemory(State.PC+1)) + uint16(mmu.ReadMemory(State.PC+2))<<8
|
||||
cycles += 6
|
||||
system.FrameCycles += 6
|
||||
|
||||
if disableFirmwareWait && value == 0xfca8 {
|
||||
State.PC += 3
|
||||
|
@ -604,38 +604,38 @@ func Run(showInstructions bool, breakAddress *uint16, disableFirmwareWait bool,
|
|||
case 0x60: // RTS
|
||||
value := pop16()
|
||||
State.PC = value + 1
|
||||
cycles += 6
|
||||
system.FrameCycles += 6
|
||||
|
||||
case 0xa9, 0xa5, 0xb5, 0xad, 0xbd, 0xb9, 0xa1, 0xb1: // LDA
|
||||
State.A = load(&cycles, addressMode)
|
||||
State.A = load(addressMode)
|
||||
case 0xa2, 0xa6, 0xb6, 0xae, 0xbe: // LDX
|
||||
State.X = load(&cycles, addressMode)
|
||||
State.X = load(addressMode)
|
||||
case 0xa0, 0xa4, 0xb4, 0xac, 0xbc: // LDY
|
||||
State.Y = load(&cycles, addressMode)
|
||||
State.Y = load(addressMode)
|
||||
|
||||
case 0x85, 0x95, 0x8d, 0x9d, 0x99, 0x81, 0x91: //STA
|
||||
store(&cycles, State.A, addressMode)
|
||||
store(State.A, addressMode)
|
||||
case 0x86, 0x96, 0x8e: // STX
|
||||
store(&cycles, State.X, addressMode)
|
||||
store(State.X, addressMode)
|
||||
case 0x84, 0x94, 0x8c: //STY
|
||||
store(&cycles, State.Y, addressMode)
|
||||
store(State.Y, addressMode)
|
||||
|
||||
case 0xc9, 0xc5, 0xd5, 0xcd, 0xdd, 0xd9, 0xc1, 0xd1: // CMP
|
||||
cmp(&cycles, State.A, addressMode)
|
||||
cmp(State.A, addressMode)
|
||||
case 0xe0, 0xe4, 0xeC: // CPX
|
||||
cmp(&cycles, State.X, addressMode)
|
||||
cmp(State.X, addressMode)
|
||||
case 0xc0, 0xc4, 0xcc: // CPY
|
||||
cmp(&cycles, State.Y, addressMode)
|
||||
cmp(State.Y, addressMode)
|
||||
case 0x09, 0x05, 0x15, 0x0d, 0x1d, 0x19, 0x01, 0x11: // ORA
|
||||
ora(&cycles, addressMode)
|
||||
ora(addressMode)
|
||||
case 0x29, 0x25, 0x35, 0x2d, 0x3d, 0x39, 0x21, 0x31: // AND
|
||||
and(&cycles, addressMode)
|
||||
and(addressMode)
|
||||
case 0x49, 0x45, 0x55, 0x4d, 0x5d, 0x59, 0x41, 0x51: // EOR
|
||||
eor(&cycles, addressMode)
|
||||
eor(addressMode)
|
||||
case 0x69, 0x65, 0x75, 0x6d, 0x7d, 0x79, 0x61, 0x71: // ADC
|
||||
adc(&cycles, addressMode)
|
||||
adc(addressMode)
|
||||
case 0xe9, 0xe5, 0xf5, 0xed, 0xfd, 0xf9, 0xe1, 0xf1: // SBC
|
||||
sbc(&cycles, addressMode)
|
||||
sbc(addressMode)
|
||||
|
||||
// Register transfers
|
||||
case 0xaa: // TAX
|
||||
|
@ -643,169 +643,169 @@ func Run(showInstructions bool, breakAddress *uint16, disableFirmwareWait bool,
|
|||
setN(State.X)
|
||||
setZ(State.X)
|
||||
State.PC++
|
||||
cycles += 2
|
||||
system.FrameCycles += 2
|
||||
case 0xa8: // TAY
|
||||
State.Y = State.A
|
||||
setN(State.Y)
|
||||
setZ(State.Y)
|
||||
State.PC++
|
||||
cycles += 2
|
||||
system.FrameCycles += 2
|
||||
case 0xba: // TSX
|
||||
State.X = State.SP
|
||||
setN(State.X)
|
||||
setZ(State.X)
|
||||
State.PC++
|
||||
cycles += 2
|
||||
system.FrameCycles += 2
|
||||
case 0x8a: // TXA
|
||||
State.A = State.X
|
||||
setN(State.A)
|
||||
setZ(State.A)
|
||||
State.PC++
|
||||
cycles += 2
|
||||
system.FrameCycles += 2
|
||||
case 0x9a: // TXS
|
||||
State.SP = State.X
|
||||
State.PC++
|
||||
cycles += 2
|
||||
system.FrameCycles += 2
|
||||
case 0x98: // TYA
|
||||
State.A = State.Y
|
||||
setN(State.A)
|
||||
setZ(State.A)
|
||||
State.PC++
|
||||
cycles += 2
|
||||
system.FrameCycles += 2
|
||||
|
||||
case 0xE8:
|
||||
State.X = (State.X + 1) & 0xff
|
||||
setN(State.X)
|
||||
setZ(State.X)
|
||||
State.PC++
|
||||
cycles += 2
|
||||
system.FrameCycles += 2
|
||||
case 0xC8:
|
||||
State.Y = (State.Y + 1) & 0xff
|
||||
setN(State.Y)
|
||||
setZ(State.Y)
|
||||
State.PC++
|
||||
cycles += 2
|
||||
system.FrameCycles += 2
|
||||
case 0xca:
|
||||
State.X = (State.X - 1) & 0xff
|
||||
setN(State.X)
|
||||
setZ(State.X)
|
||||
State.PC++
|
||||
cycles += 2
|
||||
system.FrameCycles += 2
|
||||
case 0x88:
|
||||
State.Y = (State.Y - 1) & 0xff
|
||||
setN(State.Y)
|
||||
setZ(State.Y)
|
||||
State.PC++
|
||||
cycles += 2
|
||||
system.FrameCycles += 2
|
||||
|
||||
// Branch instructions
|
||||
case 0x10:
|
||||
branch(&cycles, "BPL", !isN())
|
||||
branch("BPL", !isN())
|
||||
case 0x30:
|
||||
branch(&cycles, "BMI", isN())
|
||||
branch("BMI", isN())
|
||||
case 0x50:
|
||||
branch(&cycles, "BVC", !isV())
|
||||
branch("BVC", !isV())
|
||||
case 0x70:
|
||||
branch(&cycles, "BVS", isV())
|
||||
branch("BVS", isV())
|
||||
case 0x90:
|
||||
branch(&cycles, "BCC", !isC())
|
||||
branch("BCC", !isC())
|
||||
case 0xb0:
|
||||
branch(&cycles, "BCS", isC())
|
||||
branch("BCS", isC())
|
||||
case 0xd0:
|
||||
branch(&cycles, "BNE", !isZ())
|
||||
branch("BNE", !isZ())
|
||||
case 0xf0:
|
||||
branch(&cycles, "BEQ", isZ())
|
||||
branch("BEQ", isZ())
|
||||
|
||||
// Flag setting
|
||||
case 0x18:
|
||||
setC(false)
|
||||
State.PC++
|
||||
cycles += 2
|
||||
system.FrameCycles += 2
|
||||
case 0x38:
|
||||
setC(true)
|
||||
State.PC++
|
||||
cycles += 2
|
||||
system.FrameCycles += 2
|
||||
case 0x58:
|
||||
State.P &= ^CpuFlagI
|
||||
State.PC++
|
||||
cycles += 2
|
||||
system.FrameCycles += 2
|
||||
case 0x78:
|
||||
State.P |= CpuFlagI
|
||||
State.PC++
|
||||
cycles += 2
|
||||
system.FrameCycles += 2
|
||||
case 0xb8:
|
||||
State.P &= ^CpuFlagV
|
||||
State.PC++
|
||||
cycles += 2
|
||||
system.FrameCycles += 2
|
||||
case 0xd8:
|
||||
State.P &= ^CpuFlagD
|
||||
State.PC++
|
||||
cycles += 2
|
||||
system.FrameCycles += 2
|
||||
case 0xf8:
|
||||
State.P |= CpuFlagD
|
||||
State.PC++
|
||||
cycles += 2
|
||||
system.FrameCycles += 2
|
||||
|
||||
case 0x48: // PHA
|
||||
push8(State.A)
|
||||
State.PC++
|
||||
cycles += 3
|
||||
system.FrameCycles += 3
|
||||
case 0x68: // PLA
|
||||
State.A = pop8()
|
||||
setN(State.A)
|
||||
setZ(State.A)
|
||||
State.PC++
|
||||
cycles += 4
|
||||
system.FrameCycles += 4
|
||||
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)
|
||||
State.PC++
|
||||
cycles += 3
|
||||
system.FrameCycles += 3
|
||||
case 0x28: // PLP
|
||||
// CpuFlagR is always supposed to be 1
|
||||
State.P = pop8() | CpuFlagR
|
||||
State.PC++
|
||||
cycles += 4
|
||||
system.FrameCycles += 4
|
||||
case 0xea:
|
||||
State.PC++
|
||||
cycles += 2
|
||||
system.FrameCycles += 2
|
||||
|
||||
case 0x00: // BRK
|
||||
brk(&cycles)
|
||||
brk()
|
||||
case 0x40: // RTI
|
||||
State.P = pop8() | CpuFlagR
|
||||
value := pop16()
|
||||
State.PC = value
|
||||
cycles += 6
|
||||
system.FrameCycles += 6
|
||||
|
||||
case 0x24: // BIT $00
|
||||
address := mmu.ReadMemory(State.PC + 1)
|
||||
bit(uint16(address))
|
||||
State.PC += 2
|
||||
cycles += 3
|
||||
system.FrameCycles += 3
|
||||
case 0x2C: // BIT $0000
|
||||
address := uint16(mmu.ReadMemory(State.PC+1)) + uint16(mmu.ReadMemory(State.PC+2))<<8
|
||||
bit(address)
|
||||
State.PC += 3
|
||||
cycles += 4
|
||||
system.FrameCycles += 4
|
||||
|
||||
case 0x0a, 0x06, 0x16, 0x0e, 0x1e: // ASL
|
||||
address, value := preProcessShift(&cycles, addressMode)
|
||||
address, value := preProcessShift(addressMode)
|
||||
setC((value & 0x80) != 0)
|
||||
value = (value << 1) & 0xff
|
||||
setZ(value)
|
||||
setN(value)
|
||||
postProcessShift(&cycles, addressMode, address, value)
|
||||
postProcessShift(addressMode, address, value)
|
||||
case 0x4a, 0x46, 0x56, 0x4e, 0x5e: // LSR
|
||||
address, value := preProcessShift(&cycles, addressMode)
|
||||
address, value := preProcessShift(addressMode)
|
||||
setC((value & 0x01) != 0)
|
||||
value >>= 1
|
||||
setZ(value)
|
||||
setN(value)
|
||||
postProcessShift(&cycles, addressMode, address, value)
|
||||
postProcessShift(addressMode, address, value)
|
||||
case 0x2a, 0x26, 0x36, 0x2e, 0x3e: // ROL
|
||||
address, value := preProcessShift(&cycles, addressMode)
|
||||
address, value := preProcessShift(addressMode)
|
||||
value16 := uint16(value)
|
||||
value16 <<= 1
|
||||
if (State.P & CpuFlagC) != 0 {
|
||||
|
@ -815,9 +815,9 @@ func Run(showInstructions bool, breakAddress *uint16, disableFirmwareWait bool,
|
|||
value = uint8(value16 & 0xff)
|
||||
setZ(value)
|
||||
setN(value)
|
||||
postProcessShift(&cycles, addressMode, address, value)
|
||||
postProcessShift(addressMode, address, value)
|
||||
case 0x6a, 0x66, 0x76, 0x6e, 0x7e: // ROR
|
||||
address, value := preProcessShift(&cycles, addressMode)
|
||||
address, value := preProcessShift(addressMode)
|
||||
value16 := uint16(value)
|
||||
if (State.P & CpuFlagC) != 0 {
|
||||
value16 |= 0x100
|
||||
|
@ -826,7 +826,7 @@ func Run(showInstructions bool, breakAddress *uint16, disableFirmwareWait bool,
|
|||
value = uint8(value16 >> 1)
|
||||
setZ(value)
|
||||
setN(value)
|
||||
postProcessShift(&cycles, addressMode, address, value)
|
||||
postProcessShift(addressMode, address, value)
|
||||
|
||||
case 0xe6, 0xf6, 0xee, 0xfe: // INC
|
||||
address, _ := getAddressFromAddressMode(addressMode)
|
||||
|
@ -835,7 +835,7 @@ func Run(showInstructions bool, breakAddress *uint16, disableFirmwareWait bool,
|
|||
setZ(value)
|
||||
setN(value)
|
||||
mmu.WriteMemory(address, value)
|
||||
postProcessIncDec(&cycles, addressMode)
|
||||
postProcessIncDec(addressMode)
|
||||
|
||||
case 0xc6, 0xd6, 0xce, 0xde: // DEC
|
||||
address, _ := getAddressFromAddressMode(addressMode)
|
||||
|
@ -844,7 +844,7 @@ func Run(showInstructions bool, breakAddress *uint16, disableFirmwareWait bool,
|
|||
setZ(value)
|
||||
setN(value)
|
||||
mmu.WriteMemory(address, value)
|
||||
postProcessIncDec(&cycles, addressMode)
|
||||
postProcessIncDec(addressMode)
|
||||
|
||||
default:
|
||||
fmt.Printf("Unknown opcode $%02x\n", opcode)
|
||||
|
|
|
@ -2,6 +2,7 @@ package mmu
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"mos6502go/audio"
|
||||
"mos6502go/keyboard"
|
||||
)
|
||||
|
||||
|
@ -132,6 +133,9 @@ func driveIsreadSequencing() bool {
|
|||
// effect and the return value is meaningless
|
||||
func readWrite(address uint16, isRead bool) bool {
|
||||
switch address {
|
||||
case CLR80VID:
|
||||
// 80 column card hasn't been implemented yet
|
||||
return true
|
||||
case CLRTEXT:
|
||||
VideoState.TextMode = false
|
||||
return true
|
||||
|
@ -256,8 +260,7 @@ func ReadIO(address uint16) uint8 {
|
|||
// RDALTCH not implemented
|
||||
return 0x0d
|
||||
case SPEAKER:
|
||||
// Speaker not implemented
|
||||
// Not printing anything since this will generate a lot of noise
|
||||
audio.Click()
|
||||
case S6Q6L:
|
||||
return ReadTrackData()
|
||||
default:
|
||||
|
|
|
@ -1,14 +1,31 @@
|
|||
package system
|
||||
|
||||
var (
|
||||
PendingInterrupt bool
|
||||
PendingNMI bool
|
||||
|
||||
RunningTests bool
|
||||
RunningFunctionalTests bool
|
||||
RunningInterruptTests bool
|
||||
const (
|
||||
CpuFrequency = 1024000
|
||||
AudioSampleRate = 44100
|
||||
)
|
||||
|
||||
var (
|
||||
PendingInterrupt bool
|
||||
PendingNMI bool
|
||||
RunningTests bool
|
||||
RunningFunctionalTests bool
|
||||
RunningInterruptTests bool
|
||||
Cycles uint64
|
||||
FrameCycles uint64
|
||||
AudioCycles uint64
|
||||
AudioChannel chan uint16
|
||||
LastAudioCycles uint64
|
||||
LastAudioValue uint16
|
||||
AudioAttenuationCounter uint64
|
||||
)
|
||||
|
||||
func Init() {
|
||||
Cycles = 0
|
||||
AudioChannel = make(chan uint16, AudioSampleRate*4) // 1 second
|
||||
LastAudioValue = 0x2000
|
||||
}
|
||||
|
||||
// Handle 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
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package video
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"mos6502go/mmu"
|
||||
|
@ -20,6 +21,7 @@ var (
|
|||
flashCounter int
|
||||
flashOn bool
|
||||
loresSquares [16]*ebiten.Image
|
||||
ShowFPS bool
|
||||
)
|
||||
|
||||
func Init() {
|
||||
|
@ -57,6 +59,8 @@ func Init() {
|
|||
loresSquares[0x0D].Fill(color.NRGBA{255, 255, 0, alpha})
|
||||
loresSquares[0x0E].Fill(color.NRGBA{68, 255, 153, alpha})
|
||||
loresSquares[0x0F].Fill(color.NRGBA{255, 255, 255, alpha})
|
||||
|
||||
ShowFPS = false
|
||||
}
|
||||
|
||||
func drawText(screen *ebiten.Image, x int, y int, value uint8, flashOn bool) error {
|
||||
|
@ -147,5 +151,10 @@ func DrawScreen(screen *ebiten.Image) error {
|
|||
}
|
||||
}
|
||||
|
||||
if ShowFPS {
|
||||
msg := fmt.Sprintf(`FPS: %0.2f`, ebiten.CurrentFPS())
|
||||
ebitenutil.DebugPrint(screen, msg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user