Skip to content

Commit d44af9b

Browse files
committed
Make ppu timing (pixel X) match with nestest.
1 parent 678ea53 commit d44af9b

File tree

5 files changed

+129
-99
lines changed

5 files changed

+129
-99
lines changed

src/nes/Nes.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type Nes struct {
1515
Cpu *Cpu6502
1616
ppu *ppu.Ppu2c02
1717

18-
systemClockCounter byte // Controls how many times to call each processor
18+
systemClockCounter uint64 // Controls how many times to call each processor
1919
debug *Debugger
2020
vBlankCount byte
2121
finished bool
@@ -52,6 +52,11 @@ func (nes *Nes) StartAt(address types.Address) {
5252
nes.debug.sortedDisassembled = sortedDisassembled
5353

5454
nes.Cpu.ResetToAddress(address)
55+
// Run PPU for 7 cpu cycles. We subtract 1 because next Nes.Tick will first call PPU.
56+
for i := 0; i < (7 * 3); i++ {
57+
nes.ppu.Tick()
58+
nes.systemClockCounter++
59+
}
5560
}
5661

5762
// Start todo Rename to PowerOn
@@ -126,6 +131,7 @@ func (nes *Nes) TickForTime(seconds float64) {
126131

127132
func (nes *Nes) Tick() (byte, bool) {
128133
defer nes.handlePanic()
134+
ppuState := ppu.NewSimplePPUState(nes.ppu.FrameNumber(), nes.ppu.RenderCycle(), nes.ppu.Scanline())
129135
nes.ppu.Tick()
130136

131137
cpuCycles := byte(0)
@@ -159,7 +165,7 @@ func (nes *Nes) Tick() (byte, bool) {
159165
if nes.Cpu.debugger.Enabled {
160166
nes.Cpu.debugger.LogState(
161167
cpuState,
162-
ppu.NewSimplePPUState(nes.ppu.FrameNumber(), nes.ppu.RenderCycle(), nes.ppu.Scanline()),
168+
ppuState,
163169
)
164170
}
165171
}
@@ -200,7 +206,7 @@ func (nes *Nes) Debugger() *Debugger {
200206
return nes.debug
201207
}
202208

203-
func (nes *Nes) SystemClockCounter() byte {
209+
func (nes *Nes) SystemClockCounter() uint64 {
204210
return nes.systemClockCounter
205211
}
206212

src/nes/TestsRoms_test.go

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import (
55
"fmt"
66
"github.com/raulferras/nes-golang/src/nes/cpu"
77
gamePak2 "github.com/raulferras/nes-golang/src/nes/gamePak"
8+
ppu2 "github.com/raulferras/nes-golang/src/nes/ppu"
9+
"github.com/raulferras/nes-golang/src/nes/types"
10+
"github.com/raulferras/nes-golang/src/utils"
811
"os"
12+
"regexp"
13+
"strconv"
14+
"strings"
915
"testing"
1016
)
1117

@@ -54,15 +60,25 @@ func compareLogs(t *testing.T, snapshots []cpu.Snapshot) {
5460
for i, snapshot := range snapshots {
5561
scanner.Scan()
5662
nesTestLine := scanner.Text()
57-
nesTestState := cpu.CreateStateFromNesTestLine(nesTestLine)
58-
if !snapshot.CpuState.RegistersEquals(nesTestState) {
63+
nesTestSnapshot := CreateSnapshotFromNesTestLine(nesTestLine)
64+
if !snapshot.CpuState.RegistersEquals(nesTestSnapshot.CpuState) {
5965
msg := fmt.Sprintf("Error in iteration %d\n", i+1)
60-
msg += fmt.Sprintf("Expected: %s\n", nesTestState.ToString())
66+
msg += fmt.Sprintf("Expected: %s\n", nesTestSnapshot.CpuState.ToString())
6167
msg += fmt.Sprintf("Actual: %s\n", snapshot.CpuState.ToString())
6268

6369
t.Errorf(msg)
6470
t.FailNow()
6571
}
72+
73+
if nesTestSnapshot.CpuState.CyclesSinceReset != snapshot.CpuState.CyclesSinceReset {
74+
t.Errorf("Error in iteration %d\nCPU Cycles don't match. Expected: %d. Actual: %d", i+1, nesTestSnapshot.CpuState.CyclesSinceReset, snapshot.CpuState.CyclesSinceReset)
75+
t.FailNow()
76+
}
77+
78+
if nesTestSnapshot.PpuState.RenderCycle != snapshot.PpuState.RenderCycle {
79+
t.Errorf("Error in iteration %d\nPPU X doesn't match. Expected: %d. Actual: %d", i+1, nesTestSnapshot.PpuState.RenderCycle, snapshot.PpuState.RenderCycle)
80+
t.FailNow()
81+
}
6682
}
6783
}
6884

@@ -73,3 +89,57 @@ func TestCPUDummyReads(t *testing.T) {
7389
nes := CreateNes(&gamePak, &Debugger{})
7490
nes.Start()
7591
}
92+
93+
func CreateSnapshotFromNesTestLine(nesTestLine string) cpu.Snapshot {
94+
tokens := strings.Fields(nesTestLine)
95+
//_ = opCodeTokens
96+
97+
blocks := utils.StringSplitByRegex(nesTestLine)
98+
99+
result := utils.HexStringToByteArray(blocks[0])
100+
pc := types.CreateAddress(result[1], result[0])
101+
102+
opCodeTokens := strings.Fields(blocks[1])
103+
opcode := [3]byte{utils.HexStringToByteArray(opCodeTokens[0])[0]}
104+
105+
flagFields := strings.Fields(blocks[3])
106+
107+
r, _ := regexp.Compile("CYC:([0-9]+)$")
108+
cpuCyclesString := r.FindStringSubmatch(nesTestLine)
109+
110+
cpuCycles, _ := strconv.ParseUint(cpuCyclesString[1], 10, 16)
111+
112+
cpuState := cpu.CreateState(
113+
cpu.Registers{
114+
utils.NestestDecodeRegisterFlag(flagFields[0]),
115+
utils.NestestDecodeRegisterFlag(flagFields[1]),
116+
utils.NestestDecodeRegisterFlag(flagFields[2]),
117+
pc,
118+
utils.NestestDecodeRegisterFlag(flagFields[4]),
119+
utils.NestestDecodeRegisterFlag(flagFields[3]),
120+
},
121+
opcode,
122+
cpu.CreateInstruction(
123+
strings.Fields(blocks[2])[0],
124+
cpu.Implicit,
125+
nil,
126+
0,
127+
0,
128+
),
129+
cpu.OperationMethodArgument{0, 0},
130+
uint32(cpuCycles),
131+
)
132+
133+
ppuXIndex := len(tokens) - 2
134+
var ppuX string
135+
if strings.Contains(tokens[ppuXIndex], ",") {
136+
s := tokens[ppuXIndex]
137+
ppuX = strings.Split(s, ",")[1]
138+
} else {
139+
ppuX = tokens[ppuXIndex]
140+
}
141+
nesTestPpuX, _ := strconv.ParseUint(ppuX, 10, 16)
142+
ppu := ppu2.SimplePPUState{0, uint16(nesTestPpuX), 0}
143+
144+
return cpu.Snapshot{cpuState, ppu}
145+
}

src/nes/cpu/cpuState.go

Lines changed: 2 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import (
55
"github.com/FMNSSun/hexit"
66
"github.com/raulferras/nes-golang/src/nes/ppu"
77
"github.com/raulferras/nes-golang/src/nes/types"
8-
"github.com/raulferras/nes-golang/src/utils"
9-
"regexp"
108
"strconv"
119
"strings"
1210
)
@@ -17,11 +15,11 @@ type CpuState struct {
1715
RawOpcode [3]byte
1816
EvaluatedAddress types.Address
1917
CyclesSinceReset uint32
20-
waiting bool
18+
Waiting bool
2119
}
2220

2321
func CreateWaitingState() CpuState {
24-
return CpuState{waiting: true}
22+
return CpuState{Waiting: true}
2523
}
2624

2725
func CreateState(registers Registers, opcode [3]byte, instruction Instruction, step OperationMethodArgument, cpuCycle uint32) CpuState {
@@ -37,50 +35,6 @@ func CreateState(registers Registers, opcode [3]byte, instruction Instruction, s
3735
return state
3836
}
3937

40-
func CreateStateFromNesTestLine(nesTestLine string) CpuState {
41-
fields := strings.Fields(nesTestLine)
42-
_ = fields
43-
44-
blocks := utils.StringSplitByRegex(nesTestLine)
45-
46-
result := utils.HexStringToByteArray(blocks[0])
47-
pc := types.CreateAddress(result[1], result[0])
48-
49-
fields = strings.Fields(blocks[1])
50-
opcode := [3]byte{utils.HexStringToByteArray(fields[0])[0]}
51-
52-
flagFields := strings.Fields(blocks[3])
53-
54-
r, _ := regexp.Compile("CYC:([0-9]+)$")
55-
cpuCyclesString := r.FindStringSubmatch(nesTestLine)
56-
57-
cpuCycles, _ := strconv.ParseUint(cpuCyclesString[1], 10, 16)
58-
59-
state := CpuState{
60-
Registers{
61-
utils.NestestDecodeRegisterFlag(flagFields[0]),
62-
utils.NestestDecodeRegisterFlag(flagFields[1]),
63-
utils.NestestDecodeRegisterFlag(flagFields[2]),
64-
pc,
65-
utils.NestestDecodeRegisterFlag(flagFields[4]),
66-
utils.NestestDecodeRegisterFlag(flagFields[3]),
67-
},
68-
CreateInstruction(
69-
strings.Fields(blocks[2])[0],
70-
Implicit,
71-
nil,
72-
0,
73-
0,
74-
),
75-
opcode,
76-
types.CreateAddress(0x00, 0x00),
77-
uint32(cpuCycles),
78-
false,
79-
}
80-
81-
return state
82-
}
83-
8438
func (state *CpuState) String(ppuState ppu.SimplePPUState) string {
8539
var msg strings.Builder
8640
msg.Grow(150)

src/nes/cpu/debugger.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func (debugger *Debugger) Stop() {
3232
}
3333

3434
func (debugger *Debugger) LogState(state CpuState, ppuState ppu.SimplePPUState) {
35-
if state.waiting {
35+
if state.Waiting {
3636
return
3737
}
3838
debugger.Logger.Log(state, ppuState)

src/nes/cpu6502_handler.go

Lines changed: 44 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -27,52 +27,52 @@ func (cpu6502 *Cpu6502) ResetToAddress(programCounter types.Address) {
2727
}
2828

2929
func (cpu6502 *Cpu6502) Tick() (byte, cpu.CpuState) {
30-
if cpu6502.opCyclesLeft > 0 {
31-
cpu6502.opCyclesLeft--
32-
return cpu6502.opCyclesLeft, cpu.CreateWaitingState()
30+
var state cpu.CpuState
31+
32+
if cpu6502.opCyclesLeft == 0 {
33+
registersCopy := *cpu6502.Registers()
34+
35+
opcode := cpu6502.memory.Read(cpu6502.registers.Pc)
36+
instruction := cpu6502.instructions[opcode]
37+
cpu6502.opCyclesLeft = instruction.Cycles()
38+
39+
if instruction.Method() == nil {
40+
msg := fmt.Errorf("opcode 0x%X not implemented", opcode)
41+
cpu6502.Stop()
42+
panic(msg)
43+
}
44+
45+
operandAddress, operand, pageCrossed := cpu6502.evaluateOperandAddress(
46+
instruction.AddressMode(),
47+
cpu6502.registers.Pc+1,
48+
)
49+
step := cpu.OperationMethodArgument{
50+
instruction.AddressMode(),
51+
operandAddress,
52+
}
53+
54+
state = cpu.CreateState(
55+
registersCopy,
56+
[3]byte{opcode, operand[0], operand[1]},
57+
instruction,
58+
step,
59+
cpu6502.cycle,
60+
)
61+
62+
cpu6502.registers.Pc += types.Address(instruction.Size())
63+
64+
opMightNeedExtraCycle := instruction.Method()(step)
65+
66+
if pageCrossed && opMightNeedExtraCycle {
67+
cpu6502.opCyclesLeft++
68+
}
69+
70+
cpu6502.cycle += uint32(cpu6502.opCyclesLeft)
71+
} else {
72+
state = cpu.CreateWaitingState()
3373
}
3474

35-
if cpu6502.registers.Pc == 0xE035 {
36-
fmt.Println("breakpoint")
37-
}
38-
registersCopy := *cpu6502.Registers()
39-
40-
opcode := cpu6502.memory.Read(cpu6502.registers.Pc)
41-
instruction := cpu6502.instructions[opcode]
42-
cpu6502.opCyclesLeft = instruction.Cycles()
43-
44-
if instruction.Method() == nil {
45-
msg := fmt.Errorf("opcode 0x%X not implemented", opcode)
46-
cpu6502.Stop()
47-
panic(msg)
48-
}
49-
50-
operandAddress, operand, pageCrossed := cpu6502.evaluateOperandAddress(
51-
instruction.AddressMode(),
52-
cpu6502.registers.Pc+1,
53-
)
54-
step := cpu.OperationMethodArgument{
55-
instruction.AddressMode(),
56-
operandAddress,
57-
}
58-
59-
state := cpu.CreateState(
60-
registersCopy,
61-
[3]byte{opcode, operand[0], operand[1]},
62-
instruction,
63-
step,
64-
cpu6502.cycle,
65-
)
66-
67-
cpu6502.registers.Pc += types.Address(instruction.Size())
68-
69-
opMightNeedExtraCycle := instruction.Method()(step)
70-
71-
if pageCrossed && opMightNeedExtraCycle {
72-
cpu6502.opCyclesLeft++
73-
}
74-
75-
cpu6502.cycle += uint32(cpu6502.opCyclesLeft)
75+
cpu6502.opCyclesLeft--
7676

7777
return cpu6502.opCyclesLeft, state
7878
}

0 commit comments

Comments
 (0)