From b549d0e33df88079d7de5eaea6a62d705077e588 Mon Sep 17 00:00:00 2001 From: Will Angenent Date: Mon, 28 May 2018 14:47:16 +0100 Subject: [PATCH] Added some documentation to cpu package & made some small tweaks --- bell_test.go | 1 - cpu/cpu.go | 101 +++++++++++++++++++++++++++++++----------------- cpu/cpu_test.go | 7 ++-- cpu/debug.go | 5 +++ utils/utils.go | 1 - 5 files changed, 75 insertions(+), 40 deletions(-) diff --git a/bell_test.go b/bell_test.go index 08fb104..88850bb 100644 --- a/bell_test.go +++ b/bell_test.go @@ -19,7 +19,6 @@ func testBellCycles(delay int) { mmu.WriteMemory(0x805, 0x00) // BRK // Run the code until the BRK instruction and count the cycles - system.FrameCycles = 0 showInstructions := false breakAddress := uint16(0x805) exitAtBreak := false diff --git a/cpu/cpu.go b/cpu/cpu.go index 40c3398..8140d6c 100644 --- a/cpu/cpu.go +++ b/cpu/cpu.go @@ -9,40 +9,42 @@ import ( ) const ( - CpuFlagC byte = 1 << iota // 0x01 - CpuFlagZ // 0x02 - CpuFlagI // 0x04 - CpuFlagD // 0x08 - CpuFlagB // 0x10 - CpuFlagR // 0x20 - CpuFlagV // 0x40 - CpuFlagN // 0x80 + 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 ) var State struct { - A uint8 - X uint8 - Y uint8 - PC uint16 - SP uint8 - P uint8 + A uint8 // accumulator + X uint8 // X register + Y uint8 // Y register + PC uint16 // program counter + SP uint8 // stack pointer + 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() { system.RunningTests = false system.RunningFunctionalTests = false system.RunningInterruptTests = false + system.PendingInterrupt = false + system.PendingNMI = false + State.A = 0 State.X = 0 State.Y = 0 State.P = CpuFlagR | CpuFlagB | CpuFlagZ State.SP = 0xff - system.PendingInterrupt = false - system.PendingNMI = false } +// setC sets the carry flag func setC(value bool) { if value { State.P |= CpuFlagC @@ -51,6 +53,7 @@ func setC(value bool) { } } +// setV sets the overflow flag func setV(value bool) { if value { 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) { if (value & 0x80) != 0 { 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) { if value == 0 { State.P |= CpuFlagZ @@ -95,12 +100,14 @@ func isN() bool { 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 &= 0xff } +// push16 pushes a 16 bit value to the stack func push16(value uint16) { mmu.WritePageTable[mmu.StackPage][State.SP] = uint8(value >> 8) mmu.WritePageTable[mmu.StackPage][State.SP-1] = uint8(value & 0xff) @@ -108,12 +115,14 @@ func push16(value uint16) { State.SP &= 0xff } +// pop8 pulls an 8 bit value from the stack func pop8() uint8 { State.SP += 1 State.SP &= 0xff return mmu.ReadPageTable[mmu.StackPage][State.SP] } +// pop16 pulls a 16 bit value from the stack func pop16() uint16 { State.SP += 2 State.SP &= 0xff @@ -122,7 +131,8 @@ func pop16() uint16 { 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) var relativeAddress uint16 @@ -135,10 +145,12 @@ func branch(instructionName string, doBranch bool) { system.FrameCycles += 2 if doBranch { if system.RunningTests && State.PC == relativeAddress { + // Catch an infinite loop and exit fmt.Printf("Trap at $%04x\n", relativeAddress) os.Exit(0) } + // The number of cycles depends on if a page boundary was crossed samePage := (State.PC & 0xff00) == (relativeAddress & 0xff00) if samePage { 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) { switch addressMode { case amZeroPage: @@ -186,6 +199,7 @@ func getAddressFromAddressMode(addressMode byte) (result uint16, pageBoundaryCro return result, pageBoundaryCrossed } +// readMemoryWithAddressMode reads memory using a particular address mode func readMemoryWithAddressMode(addressMode byte) (result uint8, pageBoundaryCrossed bool) { switch addressMode { case amImmediate: @@ -239,7 +253,7 @@ func readMemoryWithAddressMode(addressMode byte) (result uint8, pageBoundaryCros return result, pageBoundaryCrossed } -// STA, STX and STY +// store handles STA, STX and STY func store(regValue uint8, addressMode byte) { address, _ := getAddressFromAddressMode(addressMode) 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) { extraCycle := uint64(0) if pageBoundaryCrossed { @@ -308,6 +322,7 @@ func advanceCyclesForAcculumatorOperation(addressMode byte, pageBoundaryCrossed } } +// LDA, LDX, LDY func load(addressMode byte) uint8 { value, pageBoundaryCrossed := readMemoryWithAddressMode(addressMode) setN(value) @@ -316,6 +331,7 @@ func load(addressMode byte) uint8 { return value } +// CMP, CPX, CPY func cmp(regValue uint8, addressMode byte) { value, pageBoundaryCrossed := readMemoryWithAddressMode(addressMode) var result uint16 @@ -532,30 +548,37 @@ func nmi() { 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) { system.FrameCycles = 0 for { + // Exit if wantedCycles is set and has been reached if (wantedCycles != 0) && (system.FrameCycles >= wantedCycles) { return } + // Exit if the magic address of the functional tests has been reached if system.RunningTests && (State.PC == 0x3869) { fmt.Println("Functional tests passed") return } + // Exit if the magic address of the interupt tests has been reached if system.RunningTests && (State.PC == 0x0af5) { fmt.Println("Interrupt tests passed") return } + // Handle an IRQ f there is one pending and interrupts are enabled if system.PendingInterrupt && ((State.P & CpuFlagI) == 0) { irq() system.PendingInterrupt = false continue } + // Handle an NMI if there is one pending if system.PendingNMI { nmi() system.PendingNMI = false @@ -566,24 +589,29 @@ func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableF PrintInstruction(true) } - opcode := mmu.ReadMemory(State.PC) - addressMode := opCodes[opcode].addressingMode.mode - + // Handle case of breakAddress being set and being been reached if breakAddress != nil && State.PC == *breakAddress { if exitAtBreak { + // Exit the process completely fmt.Printf("Break at $%04x\n", *breakAddress) PrintInstruction(true) os.Exit(0) } else { + // Exit politely return } } + // Decode opcode + opcode := mmu.ReadMemory(State.PC) + addressMode := opCodes[opcode].addressingMode.mode + switch opcode { case 0x4c: // JMP $0000 value := uint16(mmu.ReadMemory(State.PC+1)) + uint16(mmu.ReadMemory(State.PC+2))<<8 if system.RunningTests && State.PC == value { + // Check for an infinite loop and exit if so fmt.Printf("Trap at $%04x\n", value) os.Exit(0) } @@ -599,6 +627,7 @@ func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableF system.FrameCycles += 6 if disableFirmwareWait && value == 0xfca8 { + // Don't call the firmware wait, just move forward and pretend it happened. State.PC += 3 State.A = 0 continue @@ -679,25 +708,25 @@ func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableF State.PC++ system.FrameCycles += 2 - case 0xE8: + case 0xE8: // INX State.X = (State.X + 1) & 0xff setN(State.X) setZ(State.X) State.PC++ system.FrameCycles += 2 - case 0xC8: + case 0xC8: // INY State.Y = (State.Y + 1) & 0xff setN(State.Y) setZ(State.Y) State.PC++ system.FrameCycles += 2 - case 0xca: + case 0xca: // DEX State.X = (State.X - 1) & 0xff setN(State.X) setZ(State.X) State.PC++ system.FrameCycles += 2 - case 0x88: + case 0x88: // DEY State.Y = (State.Y - 1) & 0xff setN(State.Y) setZ(State.Y) @@ -706,21 +735,21 @@ func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableF // Branch instructions case 0x10: - branch("BPL", !isN()) + branch(!isN()) case 0x30: - branch("BMI", isN()) + branch(isN()) case 0x50: - branch("BVC", !isV()) + branch(!isV()) case 0x70: - branch("BVS", isV()) + branch(isV()) case 0x90: - branch("BCC", !isC()) + branch(!isC()) case 0xb0: - branch("BCS", isC()) + branch(isC()) case 0xd0: - branch("BNE", !isZ()) + branch(!isZ()) case 0xf0: - branch("BEQ", isZ()) + branch(isZ()) // Flag setting case 0x18: // CLC @@ -752,6 +781,7 @@ func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableF State.PC++ system.FrameCycles += 2 + // Stack operations case 0x48: // PHA push8(State.A) State.PC++ @@ -796,6 +826,7 @@ func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableF State.PC += 3 system.FrameCycles += 4 + // Shifts and rotations case 0x0a, 0x06, 0x16, 0x0e, 0x1e: // ASL address, value := preProcessShift(addressMode) setC((value & 0x80) != 0) diff --git a/cpu/cpu_test.go b/cpu/cpu_test.go index 897208c..a5830e7 100644 --- a/cpu/cpu_test.go +++ b/cpu/cpu_test.go @@ -1,12 +1,15 @@ 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 ( "flag" "fmt" "testing" "github.com/freewilll/apple2/cpu" - "github.com/freewilll/apple2/keyboard" "github.com/freewilll/apple2/mmu" "github.com/freewilll/apple2/system" "github.com/freewilll/apple2/utils" @@ -73,8 +76,6 @@ func TestCPU(t *testing.T) { mmu.WritePageTable[0xc0+i] = RomPretendingToBeRAM[i*0x100 : i*0x100+0x100] } - keyboard.Init() - cpu.Run(*showInstructions, breakAddress, true, false, 0) fmt.Printf("Finished running %s\n\n", rom) } diff --git a/cpu/debug.go b/cpu/debug.go index 9fc9712..451a1c8 100644 --- a/cpu/debug.go +++ b/cpu/debug.go @@ -7,6 +7,7 @@ import ( "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) { if (p & flag) == 0 { 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) { fmt.Printf("%04x- %-24s", State.PC, instruction) @@ -40,6 +42,7 @@ func printInstruction(instruction string, showRegisters bool) { fmt.Println("") } +// PrintInstruction prints the instruction at the current PC func PrintInstruction(showRegisters bool) { opcodeValue := mmu.ReadPageTable[(State.PC)>>8][(State.PC)&0xff] opcode := opCodes[opcodeValue] @@ -82,6 +85,7 @@ func PrintInstruction(showRegisters bool) { printInstruction(fmt.Sprintf("%s %s %s", opcodes, mnemonic, suffix), showRegisters) } +// AdvanceInstruction goes forward one instruction without executing anything func AdvanceInstruction() { opcodeValue := mmu.ReadPageTable[(State.PC)>>8][(State.PC)&0xff] opcode := opCodes[opcodeValue] @@ -89,6 +93,7 @@ func AdvanceInstruction() { State.PC += uint16(size) } +// DumpMemory dumps $100 bytes of memory func DumpMemory(offset uint16) { var i uint16 for i = 0; i < 0x100; i++ { diff --git a/utils/utils.go b/utils/utils.go index 2fa6dbb..184c2a6 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -57,7 +57,6 @@ func DecodeCmdLineAddress(s *string) (result *uint16) { // been reached. func RunUntilBreakPoint(t *testing.T, breakAddress uint16, seconds int, showInstructions bool, message string) { fmt.Printf("Running until %#04x: %s \n", breakAddress, message) - system.FrameCycles = 0 system.LastAudioCycles = 0 exitAtBreak := false disableFirmwareWait := false