OXYGENHUB
◆ main enhanced-x16-emulator / tests / test_debug_write_path.c
5.1 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
1
// The write half of the debugOn contract.
2
//
3
// tests/test_debugon_contract.c pins the read half: a debug read reports what
4
// is there, moves no device state and spends no cycles. This is the other
5
// half, which until recently did not exist -- write6502() took no flag, so
6
// nothing downstream could tell a debugger's edit from a store the program
7
// executed, and every consequence of a store followed.
8
//
9
// The rule, decided rather than discovered:
10
//
11
//     a debugger's write updates the machine and does nothing else. The byte
12
//     reads as though it had always been there.
13
//
14
// Two consequences follow, and each is asserted against the real-write case
15
// beside it, because either alone reads as an unremarkable fact about slow I/O
16
// or about watchpoints:
17
//
18
//   * No cycles. A debugger that spent them would change program timing by
19
//     being used, which is the same reason a debug read spends none.
20
//   * No watchpoint. A watchpoint answers "when does the program touch this?",
21
//     and the program did not.
22
//
23
// What a debug write does do is write. The device is updated, its side effects
24
// happen, and the value is there afterwards -- that is the point of the edit,
25
// and the half that must not be suppressed.
26
//
27
// ORACLE: none, and none is possible. Whether a debugger's write should be
28
// distinguishable from the program's is a design question about this emulator,
29
// not a question about hardware: the machine cannot tell that a debugger
30
// exists. These assertions pin a decision, not a measurement.
31
//
32
// Devices are recording fakes, so what is pinned is how memory.c routes and
33
// charges, not how any real device behaves.
34
35
#include "support/fake_devices.h"
36
#include "support/harness.h"
37
38
#include "debug_core.h"
39
#include "memory.h"
40
41
#include <stdint.h>
42
43
extern uint32_t clockticks6502;
44
45
// Defined in fake_devices.c, which does not declare it in its header; the
46
// existing watchpoint test externs it the same way.
47
extern int fake_break_count;
48
49
// $9F40-$9F5F is the slow IO2 range: three cycles on a real access.
50
#define ADDR_SLOW_IO 0x9F41
51
#define ADDR_VERA    0x9F23
52
#define ADDR_RAM     0x0200
53
54
static void
55
reset(void)
56
{
57
	fake_devices_reset();
58
	debug_wp_clear_all();
59
	fake_break_count = 0;
60
}
61
62
static void
63
test_a_debug_write_spends_no_cycles(void)
64
{
65
	reset();
66
	write6502(ADDR_SLOW_IO, 0, 0x42);
67
	check_eq(clockticks6502, 3, "the program's write to slow I/O spends three cycles");
68
69
	reset();
70
	debug_write6502(ADDR_SLOW_IO, 0, 0x42);
71
	check_eq(clockticks6502, 0, "the debugger's write to the same address spends none");
72
73
	// The other slow range, which charges from a different branch.
74
	reset();
75
	write6502(0x9FA0, 0, 0x42);
76
	check_eq(clockticks6502, 3, "and the same holds in the slow IO5-7 range");
77
78
	reset();
79
	debug_write6502(0x9FA0, 0, 0x42);
80
	check_eq(clockticks6502, 0, "where the debugger's write is also free");
81
82
	// Plain RAM costs nothing either way, which keeps the above honest.
83
	reset();
84
	write6502(ADDR_RAM, 0, 0x42);
85
	check_eq(clockticks6502, 0, "a write to plain RAM spends none either way");
86
}
87
88
static void
89
test_a_debug_write_reports_no_watchpoint(void)
90
{
91
	reset();
92
	debug_wp_add_for(ADDR_RAM, 1, DEBUG_BANK_ANY, DEBUG_OWNER_CLI);
93
94
	write6502(ADDR_RAM, 0, 0x11);
95
	check_eq(fake_break_count, 1, "the program's store on a watched address breaks");
96
97
	reset();
98
	debug_wp_add_for(ADDR_RAM, 1, DEBUG_BANK_ANY, DEBUG_OWNER_CLI);
99
100
	debug_write6502(ADDR_RAM, 0, 0x22);
101
	check_eq(fake_break_count, 0, "the debugger's edit of the same address does not");
102
	check_eq(read6502(ADDR_RAM, 0), 0x22, "and the byte is written regardless");
103
}
104
105
// The half that must survive: an edit is only useful if it lands.
106
static void
107
test_a_debug_write_still_updates_the_machine(void)
108
{
109
	reset();
110
	debug_write6502(ADDR_RAM, 0, 0x5A);
111
	check_eq(read6502(ADDR_RAM, 0), 0x5A, "a debug write to RAM is there afterwards");
112
113
	reset();
114
	debug_write6502(ADDR_VERA, 0, 0x42);
115
	check_eq(fake_dev[FAKE_VERA].writes, 1, "a debug write reaches the device");
116
	check_eq(fake_dev[FAKE_VERA].last_value, 0x42, "carrying its value");
117
	check_eq(fake_dev[FAKE_VERA].state, 1, "and moves it, because writing the port is the point");
118
}
119
120
// Everything the machine can see afterwards is the same. Only the things that
121
// exist for the debugger's benefit -- the clock and the watchpoint -- differ,
122
// and they are what "does nothing else" means.
123
static void
124
test_the_two_writes_leave_the_same_machine(void)
125
{
126
	reset();
127
	write6502(ADDR_VERA, 0, 0x42);
128
	const uint32_t by_program = fake_devices_state_sum();
129
	const int      writes     = fake_dev[FAKE_VERA].writes;
130
	const uint8_t  value      = fake_dev[FAKE_VERA].last_value;
131
132
	reset();
133
	debug_write6502(ADDR_VERA, 0, 0x42);
134
135
	check_eq(fake_devices_state_sum(), by_program, "both writes leave identical device state");
136
	check_eq(fake_dev[FAKE_VERA].writes, writes, "both are counted as a write");
137
	check_eq(fake_dev[FAKE_VERA].last_value, value, "and both delivered the same byte");
138
}
139
140
int
141
main(void)
142
{
143
	test_a_debug_write_spends_no_cycles();
144
	test_a_debug_write_reports_no_watchpoint();
145
	test_a_debug_write_still_updates_the_machine();
146
	test_the_two_writes_leave_the_same_machine();
147
	return x16_test_summary("debug_write_path");
148
}