Skip to content

Commit 8cdc0f3

Browse files
committed
Pause emulation on breakpoints and be able to advance step by step (cpu).
1 parent cc7c6f3 commit 8cdc0f3

File tree

13 files changed

+367
-125
lines changed

13 files changed

+367
-125
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
# Usage
44
## Arguments
55
- `-rom` Path to rom to load.
6-
6+
- `-scale` Output screen resolution, relative to native NES. > 1
7+
- `-breakpoint` setup a single breakpoint
78

89
## Shortcuts
910
- `p` Displays PPU Register debug panel.

src/debugger/breakpoints.go

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,20 @@ import (
55
"github.com/lachee/raylib-goplus/raylib"
66
"github.com/raulferras/nes-golang/src/nes"
77
"github.com/raulferras/nes-golang/src/nes/types"
8+
"log"
89
)
910

1011
type breakpointDebugger struct {
1112
emulator *nes.Nes
1213
panel *draggablePanel
1314
breakpointAddPanel *breakpointAdd
14-
breakpoints [4]uint16
15-
breakpointsCount uint8
16-
breakpointEnabled bool
15+
16+
dasmScroll int
17+
dasmActive int
18+
19+
breakpoints [4]uint16
20+
breakpointsCount uint8
21+
breakpointEnabled bool
1722
}
1823

1924
const breakpointDebuggerWidth = 500
@@ -43,23 +48,42 @@ func (dbg *breakpointDebugger) Draw() {
4348
padding := float32(5)
4449
anchor := raylib.Vector2{dbg.panel.position.X + padding, dbg.panel.position.Y + 30}
4550

51+
dbg.drawDisassembler(anchor)
52+
dbg.breakPointControls(anchor)
53+
}
54+
55+
func (dbg *breakpointDebugger) drawDisassembler(anchor raylib.Vector2) {
4656
raylib.GuiLabel(
4757
raylib.Rectangle{anchor.X, anchor.Y, 200, 20},
4858
"Disassembler",
4959
)
50-
raylib.GuiListViewEx(
60+
61+
disassembled := dbg.emulator.Debugger().SortedDisassembled()
62+
pc := dbg.emulator.Debugger().ProgramCounter()
63+
64+
// todo optimization: extract only the asm lines required, based on current dbg.dasmScroll
65+
var disasm []string
66+
disasm = make([]string, 0, len(disassembled))
67+
line := 0
68+
active := 0
69+
for _, asm := range disassembled {
70+
if asm.Address == pc {
71+
active = line
72+
//log.Printf("Current pc found: %x (idx %d)", pc, line)
73+
}
74+
disasm = append(disasm, asm.Asm)
75+
line++
76+
}
77+
78+
_, _, scroll := raylib.GuiListViewEx(
5179
raylib.Rectangle{anchor.X, anchor.Y + 20, 200, 300},
52-
[]string{
53-
"hola",
54-
"dos",
55-
},
56-
2,
80+
disasm,
81+
len(disasm),
5782
0,
58-
0,
59-
-1,
83+
active,
84+
active,
6085
)
61-
62-
dbg.breakPointControls(anchor)
86+
dbg.dasmScroll = scroll
6387
}
6488

6589
func (dbg *breakpointDebugger) breakPointControls(windowAnchor raylib.Vector2) {
@@ -104,12 +128,17 @@ func (dbg *breakpointDebugger) breakPointControls(windowAnchor raylib.Vector2) {
104128
)
105129
y = dbg.panel.registerStackedControl(20, padding)
106130

107-
raylib.GuiButton(
131+
stepClicked := raylib.GuiButton(
108132
raylib.Rectangle{anchor.X, y, 100, 20},
109133
//raylib.GuiIconText(raylib.)
110134
"Step",
111135
)
112136

137+
if stepClicked {
138+
log.Println("click on step")
139+
dbg.emulator.Debugger().RunOneCPUOperationAndPause()
140+
}
141+
113142
if dbg.breakpointsCount < 4 {
114143
if addBreakPointClicked {
115144
dbg.showBreakpointAdd()

src/main.go

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"flag"
55
"fmt"
6+
"github.com/FMNSSun/hexit"
67
r "github.com/lachee/raylib-goplus/raylib"
78
"github.com/pkg/profile"
89
"github.com/raulferras/nes-golang/src/debugger"
@@ -20,16 +21,8 @@ import (
2021
var cpuAdvance bool
2122

2223
func main() {
23-
scale, cpuprofile, romPath, debugPPU, maxCPUCycle := cmdLineArguments()
24+
scale, cpuprofile, romPath, debugPPU, breakpoint := cmdLineArguments()
2425
if cpuprofile != "" {
25-
/*f, err := os.Create(cpuprofile)
26-
if err != nil {
27-
log.Fatal(err)
28-
}
29-
pprof.StartCPUProfile(f)
30-
pprof.
31-
defer pprof.StopCPUProfile()
32-
*/
3326
defer profile.Start(profile.CPUProfile, profile.ProfilePath(".")).Stop()
3427
}
3528

@@ -57,14 +50,17 @@ func main() {
5750

5851
debugger.PrintRomInfo(&cartridge)
5952

53+
nesDebugger := nes.CreateNesDebugger(
54+
"./var",
55+
true,
56+
debugPPU,
57+
)
58+
if len(breakpoint) > 0 {
59+
nesDebugger.AddBreakPoint(types.Address(hexit.UnhexUint16Str(breakpoint)))
60+
}
6061
console := nes.CreateNes(
6162
&cartridge,
62-
nes.CreateNesDebugger(
63-
"./var",
64-
true,
65-
debugPPU,
66-
maxCPUCycle,
67-
),
63+
nesDebugger,
6864
)
6965

7066
loop(console, scale)
@@ -78,7 +74,7 @@ func loop(console *nes.Nes, scale int) {
7874
debuggerGUI := debugger.NewDebugger(console)
7975

8076
for !r.WindowShouldClose() {
81-
if console.Stopped() {
77+
if console.Finished() {
8278
break
8379
}
8480

@@ -90,8 +86,10 @@ func loop(console *nes.Nes, scale int) {
9086
}
9187

9288
// Update emulator
93-
if cpuAdvance {
89+
if !console.Paused() {
9490
console.TickForTime(dt)
91+
} else {
92+
console.PausedTick()
9593
}
9694

9795
// Draw --------------------
@@ -126,13 +124,13 @@ func drawEmulation(frame *image.RGBA) {
126124
}
127125
}
128126

129-
func cmdLineArguments() (int, string, string, bool, int64) {
127+
func cmdLineArguments() (int, string, string, bool, string) {
130128
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
131129
var romPath = flag.String("rom", "", "path to rom")
132130
var debugPPU = flag.Bool("debugPPU", false, "Displays PPU debug information")
133-
var stopAtCpuCycle = flag.Int64("maxCpuCycle", -1, "stops emulation at given cpu cycle")
134131
var scale = flag.Int("scale", 1, "scale resolution")
132+
var breakpoint = flag.String("breakpoint", "", "defines a breakpoint on start")
135133
flag.Parse()
136134

137-
return *scale, *cpuprofile, *romPath, *debugPPU, *stopAtCpuCycle
135+
return *scale, *cpuprofile, *romPath, *debugPPU, *breakpoint
138136
}

src/nes/Debugger.go

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,48 @@ package nes
33
import (
44
"github.com/raulferras/nes-golang/src/nes/ppu"
55
"github.com/raulferras/nes-golang/src/nes/types"
6+
"github.com/raulferras/nes-golang/src/utils"
67
"image"
78
"image/color"
9+
"log"
810
)
911

1012
// Debugger offers an api to interact externally with
1113
// NES components
1214
type Debugger struct {
13-
cpu *Cpu6502
14-
ppu *ppu.Ppu2c02
15-
logPath string
16-
maxCPUCycle int64
17-
disassembled map[types.Address]string
18-
DebugPPU bool
19-
debugCPU bool
15+
cpu *Cpu6502
16+
ppu *ppu.Ppu2c02
17+
logPath string
18+
disassembled map[types.Address]string
19+
sortedDisassembled []utils.ASM
20+
21+
pauseEmulation func()
22+
23+
DebugPPU bool
24+
debugCPU bool
2025
// debugging related
21-
cpuBreakPoints map[types.Address]bool
22-
cpuStepByStepMode bool
26+
cpuBreakPoints map[types.Address]bool
27+
cpuBreakPointTriggeredAt types.Address // flag breakpoint as used
28+
cpuStepByStepMode bool
29+
waitingNextCPUOperationFinishes bool
2330
}
2431

25-
func CreateNesDebugger(logPath string, debugCPU bool, debugPPU bool, maxCPUCycle int64) *Debugger {
32+
func CreateNesDebugger(logPath string, debugCPU bool, debugPPU bool) *Debugger {
2633
return &Debugger{
2734
cpu: nil,
2835
ppu: nil,
2936
logPath: logPath,
30-
maxCPUCycle: maxCPUCycle,
3137
disassembled: nil,
3238
debugCPU: debugCPU,
3339
DebugPPU: debugPPU,
3440

41+
pauseEmulation: nil,
3542
cpuBreakPoints: make(map[types.Address]bool),
3643
}
3744
}
3845

46+
// Control flow related ---------------------------
47+
3948
func (debugger *Debugger) AddBreakPoint(address types.Address) {
4049
debugger.cpuBreakPoints[address] = true
4150
}
@@ -44,21 +53,72 @@ func (debugger *Debugger) RemoveBreakPoint(address types.Address) {
4453
debugger.cpuBreakPoints[address] = false
4554
}
4655

47-
func (debugger *Debugger) shouldPauseBecauseBreakpoint() bool {
56+
func (debugger *Debugger) shouldPauseEmulation() bool {
57+
if debugger.waitingNextCPUOperationFinishes {
58+
log.Printf("Allow one Cpu instruction")
59+
return false
60+
}
61+
62+
if debugger.isBreakpointTriggered() {
63+
debugger.pauseEmulation()
64+
return true
65+
}
66+
67+
if debugger.isManualStepMode() {
68+
return true
69+
} else {
70+
}
71+
72+
return false
73+
}
74+
75+
func (debugger *Debugger) isBreakpointTriggered() bool {
4876
pc := debugger.cpu.ProgramCounter()
77+
if pc == debugger.cpuBreakPointTriggeredAt {
78+
return false
79+
}
80+
4981
enabled, exist := debugger.cpuBreakPoints[pc]
5082
if enabled && exist {
83+
log.Printf("Breakpoint reached")
5184
debugger.cpuStepByStepMode = true
85+
debugger.cpuBreakPointTriggeredAt = pc
5286
return true
5387
}
5488

5589
return false
5690
}
5791

92+
func (debugger *Debugger) isManualStepMode() bool {
93+
return debugger.cpuStepByStepMode
94+
}
95+
96+
func (debugger *Debugger) resumeFromBreakpoint() {
97+
debugger.cpuStepByStepMode = false
98+
}
99+
100+
// RunOneCPUOperationAndPause executed when user wants to just run one single cycle after having
101+
// emulation paused.
102+
func (debugger *Debugger) RunOneCPUOperationAndPause() {
103+
debugger.waitingNextCPUOperationFinishes = true
104+
}
105+
106+
// oneCpuOperationRan This method is expected to be called after the CPU runs a cycle.
107+
// This is intended to reenable the emulation pause automatically after next cpu instruction has been called.
108+
func (debugger *Debugger) oneCpuOperationRan() {
109+
debugger.waitingNextCPUOperationFinishes = false
110+
}
111+
112+
// debugging control flow related ^---------------------------
113+
58114
func (debugger *Debugger) Disassembled() map[types.Address]string {
59115
return debugger.disassembled
60116
}
61117

118+
func (debugger *Debugger) SortedDisassembled() []utils.ASM {
119+
return debugger.sortedDisassembled
120+
}
121+
62122
func (debugger *Debugger) ProgramCounter() types.Address {
63123
return debugger.cpu.ProgramCounter()
64124
}

0 commit comments

Comments
 (0)