Added some documentation to cpu package & made some small tweaks

This commit is contained in:
Will Angenent 2018-05-28 14:47:16 +01:00
parent c397f0e092
commit b549d0e33d
5 changed files with 75 additions and 40 deletions

View File

@ -19,7 +19,6 @@ func testBellCycles(delay int) {
mmu.WriteMemory(0x805, 0x00) // BRK mmu.WriteMemory(0x805, 0x00) // BRK
// Run the code until the BRK instruction and count the cycles // Run the code until the BRK instruction and count the cycles
system.FrameCycles = 0
showInstructions := false showInstructions := false
breakAddress := uint16(0x805) breakAddress := uint16(0x805)
exitAtBreak := false exitAtBreak := false

View File

@ -9,40 +9,42 @@ import (
) )
const ( const (
CpuFlagC byte = 1 << iota // 0x01 CpuFlagC byte = 1 << iota // 0x01 carry
CpuFlagZ // 0x02 CpuFlagZ // 0x02 zero
CpuFlagI // 0x04 CpuFlagI // 0x04 interrupt disable
CpuFlagD // 0x08 CpuFlagD // 0x08 decimal mode
CpuFlagB // 0x10 CpuFlagB // 0x10 break
CpuFlagR // 0x20 CpuFlagR // 0x20 reserved (unused)
CpuFlagV // 0x40 CpuFlagV // 0x40 overflow
CpuFlagN // 0x80 CpuFlagN // 0x80 sign/negative
) )
var State struct { var State struct {
A uint8 A uint8 // accumulator
X uint8 X uint8 // X register
Y uint8 Y uint8 // Y register
PC uint16 PC uint16 // program counter
SP uint8 SP uint8 // stack pointer
P uint8 P uint8 // processor flags
} }
// Init the CPU registers, interrupts and disable testing code // Init sets up the CPU registers, interrupts and disable testing code
func Init() { func Init() {
system.RunningTests = false system.RunningTests = false
system.RunningFunctionalTests = false system.RunningFunctionalTests = false
system.RunningInterruptTests = false system.RunningInterruptTests = false
system.PendingInterrupt = false
system.PendingNMI = false
State.A = 0 State.A = 0
State.X = 0 State.X = 0
State.Y = 0 State.Y = 0
State.P = CpuFlagR | CpuFlagB | CpuFlagZ State.P = CpuFlagR | CpuFlagB | CpuFlagZ
State.SP = 0xff State.SP = 0xff
system.PendingInterrupt = false
system.PendingNMI = false
} }
// setC sets the carry flag
func setC(value bool) { func setC(value bool) {
if value { if value {
State.P |= CpuFlagC State.P |= CpuFlagC
@ -51,6 +53,7 @@ func setC(value bool) {
} }
} }
// setV sets the overflow flag
func setV(value bool) { func setV(value bool) {
if value { if value {
State.P |= CpuFlagV State.P |= CpuFlagV
@ -59,6 +62,7 @@ func setV(value bool) {
} }
} }
// setN sets the sign/negative flag if the value is negative (>=0x80)
func setN(value uint8) { func setN(value uint8) {
if (value & 0x80) != 0 { if (value & 0x80) != 0 {
State.P |= CpuFlagN State.P |= CpuFlagN
@ -67,6 +71,7 @@ func setN(value uint8) {
} }
} }
// setZ sets the zero flag if the value is zero
func setZ(value uint8) { func setZ(value uint8) {
if value == 0 { if value == 0 {
State.P |= CpuFlagZ State.P |= CpuFlagZ
@ -95,12 +100,14 @@ 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) { func push8(value uint8) {
mmu.WritePageTable[mmu.StackPage][State.SP] = value mmu.WritePageTable[mmu.StackPage][State.SP] = value
State.SP -= 1 State.SP -= 1
State.SP &= 0xff State.SP &= 0xff
} }
// push16 pushes a 16 bit value to the stack
func push16(value uint16) { func push16(value uint16) {
mmu.WritePageTable[mmu.StackPage][State.SP] = uint8(value >> 8) mmu.WritePageTable[mmu.StackPage][State.SP] = uint8(value >> 8)
mmu.WritePageTable[mmu.StackPage][State.SP-1] = uint8(value & 0xff) mmu.WritePageTable[mmu.StackPage][State.SP-1] = uint8(value & 0xff)
@ -108,12 +115,14 @@ func push16(value uint16) {
State.SP &= 0xff State.SP &= 0xff
} }
// pop8 pulls an 8 bit value from the stack
func pop8() uint8 { func pop8() uint8 {
State.SP += 1 State.SP += 1
State.SP &= 0xff State.SP &= 0xff
return mmu.ReadPageTable[mmu.StackPage][State.SP] return mmu.ReadPageTable[mmu.StackPage][State.SP]
} }
// pop16 pulls a 16 bit value from the stack
func pop16() uint16 { func pop16() uint16 {
State.SP += 2 State.SP += 2
State.SP &= 0xff State.SP &= 0xff
@ -122,7 +131,8 @@ func pop16() uint16 {
return lsb + msb<<8 return lsb + msb<<8
} }
func branch(instructionName string, doBranch bool) { // branch handles a branch instruction
func branch(doBranch bool) {
value := mmu.ReadMemory(State.PC + 1) value := mmu.ReadMemory(State.PC + 1)
var relativeAddress uint16 var relativeAddress uint16
@ -135,10 +145,12 @@ func branch(instructionName string, doBranch bool) {
system.FrameCycles += 2 system.FrameCycles += 2
if doBranch { if doBranch {
if system.RunningTests && State.PC == relativeAddress { if system.RunningTests && State.PC == relativeAddress {
// Catch an infinite loop and exit
fmt.Printf("Trap at $%04x\n", relativeAddress) fmt.Printf("Trap at $%04x\n", relativeAddress)
os.Exit(0) os.Exit(0)
} }
// The number of cycles depends on if a page boundary was crossed
samePage := (State.PC & 0xff00) == (relativeAddress & 0xff00) samePage := (State.PC & 0xff00) == (relativeAddress & 0xff00)
if samePage { if samePage {
system.FrameCycles += 1 system.FrameCycles += 1
@ -151,6 +163,7 @@ func branch(instructionName string, doBranch bool) {
} }
} }
// getAddressFromAddressMode gets the address an instruction is referring to
func getAddressFromAddressMode(addressMode byte) (result uint16, pageBoundaryCrossed bool) { func getAddressFromAddressMode(addressMode byte) (result uint16, pageBoundaryCrossed bool) {
switch addressMode { switch addressMode {
case amZeroPage: case amZeroPage:
@ -186,6 +199,7 @@ func getAddressFromAddressMode(addressMode byte) (result uint16, pageBoundaryCro
return result, pageBoundaryCrossed return result, pageBoundaryCrossed
} }
// readMemoryWithAddressMode reads memory using a particular address mode
func readMemoryWithAddressMode(addressMode byte) (result uint8, pageBoundaryCrossed bool) { func readMemoryWithAddressMode(addressMode byte) (result uint8, pageBoundaryCrossed bool) {
switch addressMode { switch addressMode {
case amImmediate: case amImmediate:
@ -239,7 +253,7 @@ func readMemoryWithAddressMode(addressMode byte) (result uint8, pageBoundaryCros
return result, pageBoundaryCrossed return result, pageBoundaryCrossed
} }
// STA, STX and STY // store handles STA, STX and STY
func store(regValue uint8, addressMode byte) { func store(regValue uint8, addressMode byte) {
address, _ := getAddressFromAddressMode(addressMode) address, _ := getAddressFromAddressMode(addressMode)
mmu.WriteMemory(address, regValue) mmu.WriteMemory(address, regValue)
@ -277,7 +291,7 @@ func store(regValue uint8, addressMode byte) {
} }
} }
// These instructions take the same amount of system.FrameCycles // advanceCyclesForAcculumatorOperation advances the number of cycles for common accumulator operations
func advanceCyclesForAcculumatorOperation(addressMode byte, pageBoundaryCrossed bool) { func advanceCyclesForAcculumatorOperation(addressMode byte, pageBoundaryCrossed bool) {
extraCycle := uint64(0) extraCycle := uint64(0)
if pageBoundaryCrossed { if pageBoundaryCrossed {
@ -308,6 +322,7 @@ func advanceCyclesForAcculumatorOperation(addressMode byte, pageBoundaryCrossed
} }
} }
// LDA, LDX, LDY
func load(addressMode byte) uint8 { func load(addressMode byte) uint8 {
value, pageBoundaryCrossed := readMemoryWithAddressMode(addressMode) value, pageBoundaryCrossed := readMemoryWithAddressMode(addressMode)
setN(value) setN(value)
@ -316,6 +331,7 @@ func load(addressMode byte) uint8 {
return value return value
} }
// CMP, CPX, CPY
func cmp(regValue uint8, addressMode byte) { func cmp(regValue uint8, addressMode byte) {
value, pageBoundaryCrossed := readMemoryWithAddressMode(addressMode) value, pageBoundaryCrossed := readMemoryWithAddressMode(addressMode)
var result uint16 var result uint16
@ -532,30 +548,37 @@ func nmi() {
system.FrameCycles += 7 system.FrameCycles += 7
} }
// Run runs the CPU until either wantedCycles has been reached (if non-zero) or the program counter reaches breakAddress.
// system.FrameCycles is the amount of cycles executed so far.
func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableFirmwareWait bool, wantedCycles uint64) { func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableFirmwareWait bool, wantedCycles uint64) {
system.FrameCycles = 0 system.FrameCycles = 0
for { for {
// Exit if wantedCycles is set and has been reached
if (wantedCycles != 0) && (system.FrameCycles >= wantedCycles) { if (wantedCycles != 0) && (system.FrameCycles >= wantedCycles) {
return return
} }
// Exit if the magic address of the functional tests has been reached
if system.RunningTests && (State.PC == 0x3869) { if system.RunningTests && (State.PC == 0x3869) {
fmt.Println("Functional tests passed") fmt.Println("Functional tests passed")
return return
} }
// Exit if the magic address of the interupt tests has been reached
if system.RunningTests && (State.PC == 0x0af5) { if system.RunningTests && (State.PC == 0x0af5) {
fmt.Println("Interrupt tests passed") fmt.Println("Interrupt tests passed")
return return
} }
// 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() irq()
system.PendingInterrupt = false system.PendingInterrupt = false
continue continue
} }
// Handle an NMI if there is one pending
if system.PendingNMI { if system.PendingNMI {
nmi() nmi()
system.PendingNMI = false system.PendingNMI = false
@ -566,24 +589,29 @@ func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableF
PrintInstruction(true) PrintInstruction(true)
} }
opcode := mmu.ReadMemory(State.PC) // Handle case of breakAddress being set and being been reached
addressMode := opCodes[opcode].addressingMode.mode
if breakAddress != nil && State.PC == *breakAddress { if breakAddress != nil && State.PC == *breakAddress {
if exitAtBreak { if exitAtBreak {
// Exit the process completely
fmt.Printf("Break at $%04x\n", *breakAddress) fmt.Printf("Break at $%04x\n", *breakAddress)
PrintInstruction(true) PrintInstruction(true)
os.Exit(0) os.Exit(0)
} else { } else {
// Exit politely
return return
} }
} }
// Decode opcode
opcode := mmu.ReadMemory(State.PC)
addressMode := opCodes[opcode].addressingMode.mode
switch opcode { switch opcode {
case 0x4c: // JMP $0000 case 0x4c: // JMP $0000
value := uint16(mmu.ReadMemory(State.PC+1)) + uint16(mmu.ReadMemory(State.PC+2))<<8 value := uint16(mmu.ReadMemory(State.PC+1)) + uint16(mmu.ReadMemory(State.PC+2))<<8
if system.RunningTests && State.PC == value { if system.RunningTests && State.PC == value {
// Check for an infinite loop and exit if so
fmt.Printf("Trap at $%04x\n", value) fmt.Printf("Trap at $%04x\n", value)
os.Exit(0) os.Exit(0)
} }
@ -599,6 +627,7 @@ func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableF
system.FrameCycles += 6 system.FrameCycles += 6
if disableFirmwareWait && value == 0xfca8 { if disableFirmwareWait && value == 0xfca8 {
// Don't call the firmware wait, just move forward and pretend it happened.
State.PC += 3 State.PC += 3
State.A = 0 State.A = 0
continue continue
@ -679,25 +708,25 @@ func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableF
State.PC++ State.PC++
system.FrameCycles += 2 system.FrameCycles += 2
case 0xE8: case 0xE8: // INX
State.X = (State.X + 1) & 0xff State.X = (State.X + 1) & 0xff
setN(State.X) setN(State.X)
setZ(State.X) setZ(State.X)
State.PC++ State.PC++
system.FrameCycles += 2 system.FrameCycles += 2
case 0xC8: case 0xC8: // INY
State.Y = (State.Y + 1) & 0xff State.Y = (State.Y + 1) & 0xff
setN(State.Y) setN(State.Y)
setZ(State.Y) setZ(State.Y)
State.PC++ State.PC++
system.FrameCycles += 2 system.FrameCycles += 2
case 0xca: case 0xca: // DEX
State.X = (State.X - 1) & 0xff State.X = (State.X - 1) & 0xff
setN(State.X) setN(State.X)
setZ(State.X) setZ(State.X)
State.PC++ State.PC++
system.FrameCycles += 2 system.FrameCycles += 2
case 0x88: case 0x88: // DEY
State.Y = (State.Y - 1) & 0xff State.Y = (State.Y - 1) & 0xff
setN(State.Y) setN(State.Y)
setZ(State.Y) setZ(State.Y)
@ -706,21 +735,21 @@ func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableF
// Branch instructions // Branch instructions
case 0x10: case 0x10:
branch("BPL", !isN()) branch(!isN())
case 0x30: case 0x30:
branch("BMI", isN()) branch(isN())
case 0x50: case 0x50:
branch("BVC", !isV()) branch(!isV())
case 0x70: case 0x70:
branch("BVS", isV()) branch(isV())
case 0x90: case 0x90:
branch("BCC", !isC()) branch(!isC())
case 0xb0: case 0xb0:
branch("BCS", isC()) branch(isC())
case 0xd0: case 0xd0:
branch("BNE", !isZ()) branch(!isZ())
case 0xf0: case 0xf0:
branch("BEQ", isZ()) branch(isZ())
// Flag setting // Flag setting
case 0x18: // CLC case 0x18: // CLC
@ -752,6 +781,7 @@ func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableF
State.PC++ State.PC++
system.FrameCycles += 2 system.FrameCycles += 2
// Stack operations
case 0x48: // PHA case 0x48: // PHA
push8(State.A) push8(State.A)
State.PC++ State.PC++
@ -796,6 +826,7 @@ func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableF
State.PC += 3 State.PC += 3
system.FrameCycles += 4 system.FrameCycles += 4
// Shifts and rotations
case 0x0a, 0x06, 0x16, 0x0e, 0x1e: // ASL case 0x0a, 0x06, 0x16, 0x0e, 0x1e: // ASL
address, value := preProcessShift(addressMode) address, value := preProcessShift(addressMode)
setC((value & 0x80) != 0) setC((value & 0x80) != 0)

View File

@ -1,12 +1,15 @@
package cpu_test package cpu_test
// Test the CPU using the functional and interrupt tests defined in the *.a65
// files and compiled to bin.gz files. The cpu package is aware of tests being run and
// will exit or bail on success and failure certain conditions.
import ( import (
"flag" "flag"
"fmt" "fmt"
"testing" "testing"
"github.com/freewilll/apple2/cpu" "github.com/freewilll/apple2/cpu"
"github.com/freewilll/apple2/keyboard"
"github.com/freewilll/apple2/mmu" "github.com/freewilll/apple2/mmu"
"github.com/freewilll/apple2/system" "github.com/freewilll/apple2/system"
"github.com/freewilll/apple2/utils" "github.com/freewilll/apple2/utils"
@ -73,8 +76,6 @@ func TestCPU(t *testing.T) {
mmu.WritePageTable[0xc0+i] = RomPretendingToBeRAM[i*0x100 : i*0x100+0x100] mmu.WritePageTable[0xc0+i] = RomPretendingToBeRAM[i*0x100 : i*0x100+0x100]
} }
keyboard.Init()
cpu.Run(*showInstructions, breakAddress, true, false, 0) cpu.Run(*showInstructions, breakAddress, true, false, 0)
fmt.Printf("Finished running %s\n\n", rom) fmt.Printf("Finished running %s\n\n", rom)
} }

View File

@ -7,6 +7,7 @@ import (
"github.com/freewilll/apple2/mmu" "github.com/freewilll/apple2/mmu"
) )
// printFlag prints a lower or uppercase letter depending on the state of the flag
func printFlag(p byte, flag uint8, code string) { func printFlag(p byte, flag uint8, code string) {
if (p & flag) == 0 { if (p & flag) == 0 {
fmt.Print(code) fmt.Print(code)
@ -15,6 +16,7 @@ func printFlag(p byte, flag uint8, code string) {
} }
} }
// printInstruction prings a single instruction and optionally also registers
func printInstruction(instruction string, showRegisters bool) { func printInstruction(instruction string, showRegisters bool) {
fmt.Printf("%04x- %-24s", State.PC, instruction) fmt.Printf("%04x- %-24s", State.PC, instruction)
@ -40,6 +42,7 @@ func printInstruction(instruction string, showRegisters bool) {
fmt.Println("") fmt.Println("")
} }
// PrintInstruction prints the instruction at the current PC
func PrintInstruction(showRegisters bool) { func PrintInstruction(showRegisters bool) {
opcodeValue := mmu.ReadPageTable[(State.PC)>>8][(State.PC)&0xff] opcodeValue := mmu.ReadPageTable[(State.PC)>>8][(State.PC)&0xff]
opcode := opCodes[opcodeValue] opcode := opCodes[opcodeValue]
@ -82,6 +85,7 @@ func PrintInstruction(showRegisters bool) {
printInstruction(fmt.Sprintf("%s %s %s", opcodes, mnemonic, suffix), showRegisters) printInstruction(fmt.Sprintf("%s %s %s", opcodes, mnemonic, suffix), showRegisters)
} }
// AdvanceInstruction goes forward one instruction without executing anything
func AdvanceInstruction() { func AdvanceInstruction() {
opcodeValue := mmu.ReadPageTable[(State.PC)>>8][(State.PC)&0xff] opcodeValue := mmu.ReadPageTable[(State.PC)>>8][(State.PC)&0xff]
opcode := opCodes[opcodeValue] opcode := opCodes[opcodeValue]
@ -89,6 +93,7 @@ func AdvanceInstruction() {
State.PC += uint16(size) State.PC += uint16(size)
} }
// DumpMemory dumps $100 bytes of memory
func DumpMemory(offset uint16) { func DumpMemory(offset uint16) {
var i uint16 var i uint16
for i = 0; i < 0x100; i++ { for i = 0; i < 0x100; i++ {

View File

@ -57,7 +57,6 @@ func DecodeCmdLineAddress(s *string) (result *uint16) {
// been reached. // been reached.
func RunUntilBreakPoint(t *testing.T, breakAddress uint16, seconds int, showInstructions bool, message string) { func RunUntilBreakPoint(t *testing.T, breakAddress uint16, seconds int, showInstructions bool, message string) {
fmt.Printf("Running until %#04x: %s \n", breakAddress, message) fmt.Printf("Running until %#04x: %s \n", breakAddress, message)
system.FrameCycles = 0
system.LastAudioCycles = 0 system.LastAudioCycles = 0
exitAtBreak := false exitAtBreak := false
disableFirmwareWait := false disableFirmwareWait := false