From 3f916a60fa7112b6ecad9ac84d84826f2a27e3b8 Mon Sep 17 00:00:00 2001 From: Ivan Izaguirre Date: Sat, 25 Sep 2021 20:33:45 +0200 Subject: [PATCH] First run with https://github.com/TomHarte/ProcessorTests --- core6502/addressing.go | 10 +++- core6502/harteSuite_test.go | 115 ++++++++++++++++++++++++++++++++++++ core6502/memory.go | 9 +++ core6502/nmos6502.go | 4 +- core6502/operations.go | 9 ++- core6502/registers.go | 5 ++ 6 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 core6502/harteSuite_test.go diff --git a/core6502/addressing.go b/core6502/addressing.go index 730413f..d0e0440 100644 --- a/core6502/addressing.go +++ b/core6502/addressing.go @@ -92,6 +92,14 @@ func resolveAddress(s *State, line []uint8, opcode opcode) uint16 { case modeIndirect: addressAddress := getWordInLine(line) address = getWord(s.mem, addressAddress) + //address = getWordNoCrossPage(s.mem, addressAddress) + + /* + The tests from https://github.com/Klaus2m5/6502_65C02_functional_tests + pass with getWord(), but the tests in https://github.com/TomHarte/ProcessorTests/tree/main/6502/v1 + need getWordNoCrossPage(). + */ + case modeIndirectIndexedY: base := getZeroPageWord(s.mem, line[1]) address, extraCycle = addOffset(base, s.reg.getY()) @@ -162,7 +170,7 @@ func lineString(line []uint8, opcode opcode) string { case modeImplicitY: //Nothing case modeAccumulator: - t += fmt.Sprintf(" A") + t += " A" case modeImmediate: t += fmt.Sprintf(" #$%02x", line[1]) case modeZeroPage: diff --git a/core6502/harteSuite_test.go b/core6502/harteSuite_test.go new file mode 100644 index 0000000..5d2c11f --- /dev/null +++ b/core6502/harteSuite_test.go @@ -0,0 +1,115 @@ +package core6502 + +import ( + "encoding/json" + "fmt" + "os" + "testing" +) + +type scenarioState struct { + Pc uint16 + S uint8 + A uint8 + X uint8 + Y uint8 + P uint8 + Ram [][]uint16 +} + +type scenario struct { + Name string + Initial scenarioState + Final scenarioState + Cycles [][]interface{} +} + +/* + Tests from https://github.com/TomHarte/ProcessorTests/tree/main/6502/v1 + more work needed. +*/ +func TestHarteNMOS6502(t *testing.T) { + t.Skip("Not ready to be used in CI") + + s := NewNMOS6502(nil) // Use to get the opcodes names + + path := "/home/casa/code/ProcessorTests/6502/v1/" + for i := 0x00; i <= 0xff; /*0xff*/ i++ { + if s.opcodes[i].name != "ADC" && // Issue with ADC crossing page boundaries + s.opcodes[i].name != "SBC" && // Issue with ADC crossing page boundaries + s.opcodes[i].name != "" { + + opcode := fmt.Sprintf("%02x", i) + t.Run(opcode+s.opcodes[i].name, func(t *testing.T) { + t.Parallel() + testOpcode(t, path, opcode) + }) + } + } +} + +func testOpcode(t *testing.T, path string, opcode string) { + m := new(FlatMemory) + s := NewNMOS6502(m) + + data, err := os.ReadFile(path + opcode + ".json") + if err != nil { + t.Fatal(err) + } + + var scenarios []scenario + err = json.Unmarshal(data, &scenarios) + if err != nil { + t.Fatal(err) + } + + for _, scenario := range scenarios { + if scenario.Name != "20 55 13" { // Skip JSR on the stack being modified + t.Run(scenario.Name, func(t *testing.T) { + testScenario(t, s, &scenario) + }) + } + } +} + +func testScenario(t *testing.T, s *State, sc *scenario) { + // Setup CPU + s.reg.setPC(sc.Initial.Pc) + s.reg.setSP(sc.Initial.S) + s.reg.setA(sc.Initial.A) + s.reg.setX(sc.Initial.X) + s.reg.setY(sc.Initial.Y) + s.reg.setP(sc.Initial.P) + for _, e := range sc.Initial.Ram { + s.mem.Poke(uint16(e[0]), uint8(e[1])) + } + + // Execute instruction + s.ExecuteInstruction() + + // Check result + assertReg8(t, sc, "A", s.reg.getA(), sc.Final.A) + assertReg8(t, sc, "X", s.reg.getX(), sc.Final.X) + assertReg8(t, sc, "Y", s.reg.getY(), sc.Final.Y) + assertFlags(t, sc, sc.Initial.P, s.reg.getP(), sc.Final.P) + assertReg8(t, sc, "SP", s.reg.getSP(), sc.Final.S) + assertReg16(t, sc, "PC", s.reg.getPC(), sc.Final.Pc) +} + +func assertReg8(t *testing.T, sc *scenario, name string, actual uint8, wanted uint8) { + if actual != wanted { + t.Errorf("Register %s is %02x and should be %02x for %+v", name, actual, wanted, sc) + } +} + +func assertReg16(t *testing.T, sc *scenario, name string, actual uint16, wanted uint16) { + if actual != wanted { + t.Errorf("Register %s is %04x and should be %04x for %+v", name, actual, wanted, sc) + } +} + +func assertFlags(t *testing.T, sc *scenario, initial uint8, actual uint8, wanted uint8) { + if actual != wanted { + t.Errorf("%08b flag diffs, they are %08b and should be %08b, initial %08b for %+v", actual^wanted, actual, wanted, initial, sc) + } +} diff --git a/core6502/memory.go b/core6502/memory.go index 178081c..b529584 100644 --- a/core6502/memory.go +++ b/core6502/memory.go @@ -16,6 +16,15 @@ func getWord(m Memory, address uint16) uint16 { return uint16(m.Peek(address)) + 0x100*uint16(m.Peek(address+1)) } +func getWordNoCrossPage(m Memory, address uint16) uint16 { + addressMSB := address + 1 + if address&0xff == 0xff { + // We won't cross the page bounday for the MSB byte + addressMSB -= 0x100 + } + return uint16(m.Peek(address)) + 0x100*uint16(m.Peek(addressMSB)) +} + func getZeroPageWord(m Memory, address uint8) uint16 { return uint16(m.Peek(uint16(address))) + 0x100*uint16(m.Peek(uint16(address+1))) } diff --git a/core6502/nmos6502.go b/core6502/nmos6502.go index ed1897b..88b1ac1 100644 --- a/core6502/nmos6502.go +++ b/core6502/nmos6502.go @@ -184,6 +184,6 @@ var opcodesNMOS6502 = [256]opcode{ // Undocumented opcodes, see http://bbc.nvg.org/doc/6502OpList.txt 0x1A: {"NOP", 1, 2, modeImplicit, opNOP}, // INC A in the 65c02 - 0xC2: {"NOP", 1, 2, modeImplicit, opNOP}, // Should be HALT? - 0x02: {"NOP", 1, 2, modeImplicit, opNOP}, // Should be HALT? + 0xC2: {"NOP", 2, 2, modeImplicit, opNOP}, + 0x02: {"NOP", 1, 1, modeImplicit, opHALT}, } diff --git a/core6502/operations.go b/core6502/operations.go index 26814c2..0a53dd2 100644 --- a/core6502/operations.go +++ b/core6502/operations.go @@ -255,7 +255,9 @@ func buildOpPull(regDst int) opFunc { return func(s *State, line []uint8, opcode opcode) { value := pullByte(s) s.reg.setRegister(regDst, value) - if regDst != regP { + if regDst == regP { + s.reg.updateFlag5B() + } else { s.reg.updateFlagZN(value) } } @@ -278,6 +280,10 @@ func opJMP(s *State, line []uint8, opcode opcode) { func opNOP(s *State, line []uint8, opcode opcode) {} +func opHALT(s *State, line []uint8, opcode opcode) { + s.reg.setPC(s.reg.getPC() - 1) +} + func opJSR(s *State, line []uint8, opcode opcode) { pushWord(s, s.reg.getPC()-1) address := resolveAddress(s, line, opcode) @@ -286,6 +292,7 @@ func opJSR(s *State, line []uint8, opcode opcode) { func opRTI(s *State, line []uint8, opcode opcode) { s.reg.setP(pullByte(s)) + s.reg.updateFlag5B() s.reg.setPC(pullWord(s)) } diff --git a/core6502/registers.go b/core6502/registers.go index 90d2f7b..2fad5f3 100644 --- a/core6502/registers.go +++ b/core6502/registers.go @@ -86,6 +86,11 @@ func (r *registers) updateFlagZN(t uint8) { r.updateFlag(flagN, t >= (1<<7)) } +func (r *registers) updateFlag5B() { + r.setFlag(flag5) + r.clearFlag(flagB) +} + func (r registers) String() string { //ch := (r.getA() & 0x3F) + 0x40 ch := (r.getA() & 0x7F)