OXYGENHUB
◆ main enhanced-x16-emulator / tests / test_memory_banking.c
9.7 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
1
// RAM and ROM banking: the registers at $0000 and $0001, and the two windows
2
// they steer.
3
//
4
// This one needs its oracle stated carefully, because it is weaker than
5
// anywhere else in the suite and the file is deliberately split along that
6
// line.
7
//
8
// VERA is an FPGA, so its Verilog is the hardware and the VERA tests cite it.
9
// The X16's banking has no equivalent: it is discrete logic on the board, and
10
// no schematic, CPLD, GAL or PLD source is published in any X16Community
11
// repository. The strongest source that exists is the project's own
12
// documentation, which is a step removed from the hardware.
13
//
14
// So:
15
//
16
//   ORACLE: C -- X16Community/x16-docs, "X16 Reference - 08 - Memory Map.md",
17
//   pinned at 79a8303bc33cda4ec3e85f3239f9b3d4c8c76260, for the bank
18
//   registers, the window sizes and the bank counts. Quoted inline. Fetch it
19
//   with `python tests/refs/fetch_refs.py`.
20
//
21
//   ORACLE: none, for what happens on a bank that is not installed. The
22
//   documentation says only that such banks are "unavailable" and never says
23
//   what accessing one does. Those assertions are characterization: they pin
24
//   what this emulator does today so a change is deliberate, and they are not
25
//   a claim about hardware. They are grouped separately below and say so.
26
//
27
// The fixture is a 512 KB machine -- fake_devices.c sets num_ram_banks to 64 --
28
// which is one of the two configurations the documentation names, and puts the
29
// boundary at exactly the bank number it calls out.
30
31
#include "support/fake_devices.h"
32
#include "support/harness.h"
33
34
#include "memory.h"
35
36
#include <stdint.h>
37
38
#define RAM_BANK_REG 0x0000
39
#define ROM_BANK_REG 0x0001
40
41
#define BANKED_RAM_START 0xA000
42
#define BANKED_RAM_END   0xBFFF
43
#define BANKED_ROM_START 0xC000
44
45
// fake_devices.c declares this as the installed RAM bank count. Stated here as
46
// a number so a change to the fixture shows up as a failure rather than
47
// quietly moving what "out of range" means.
48
#define INSTALLED_RAM_BANKS 64
49
50
static void
51
reset(void)
52
{
53
	fake_devices_reset();
54
	memory_reset();
55
}
56
57
static uint8_t
58
read(uint16_t address)
59
{
60
	return read6502(address, 0);
61
}
62
63
static void
64
write(uint16_t address, uint8_t value)
65
{
66
	write6502(address, 0, value);
67
}
68
69
// ─── Conformance: the documented shape ──────────────────────────────────────
70
71
// Memory Map, the banking register table:
72
//     |$0000    |Current RAM bank (0-255)                                      |
73
//     |$0001    |Current ROM/Cartridge bank (ROM is 0-31, Cartridge is 32-255) |
74
//
75
// and directly beneath it:
76
//     "The currently set banks can also be read back from the respective
77
//      memory locations. Both settings default to 0 on RESET."
78
static void
79
test_the_bank_registers_hold_and_report_a_bank(void)
80
{
81
	reset();
82
	check_eq(read(RAM_BANK_REG), 0, "the RAM bank defaults to 0 on reset");
83
	check_eq(read(ROM_BANK_REG), 0, "and so does the ROM bank");
84
85
	write(RAM_BANK_REG, 0x2A);
86
	check_eq(read(RAM_BANK_REG), 0x2A, "the RAM bank reads back what was written");
87
	check_eq(memory_get_ram_bank(), 0x2A, "and the machine is switched to it");
88
89
	write(ROM_BANK_REG, 0x1F);
90
	check_eq(read(ROM_BANK_REG), 0x1F, "the ROM bank reads back what was written");
91
	check_eq(memory_get_rom_bank(), 0x1F, "and the machine is switched to it");
92
93
	check_eq(read(RAM_BANK_REG), 0x2A, "with the two registers independent");
94
95
	// "0-255": the whole byte is the bank number, with nothing reserved.
96
	write(RAM_BANK_REG, 0xFF);
97
	check_eq(read(RAM_BANK_REG), 0xFF, "the RAM bank spans the whole byte");
98
	check_eq(memory_get_ram_bank(), 0xFF, "and reaches the latch unmasked");
99
	write(ROM_BANK_REG, 0xFF);
100
	check_eq(read(ROM_BANK_REG), 0xFF, "and so does the ROM bank");
101
	check_eq(memory_get_rom_bank(), 0xFF, "also unmasked");
102
}
103
104
// The other half of the same sentence: "Both settings default to 0 on RESET."
105
//
106
// On the machine $0000 and $0001 are the bank latches themselves, so a reset
107
// that clears the latches is a reset of what those addresses report. The
108
// emulator keeps the two apart -- memory.c:325 writes the byte into RAM as
109
// well as into the latch, and memory.c:220 reads it back out of RAM.
110
static void
111
test_a_reset_returns_both_banks_to_zero(void)
112
{
113
	reset();
114
	write(RAM_BANK_REG, 0xFF);
115
	write(ROM_BANK_REG, 0xFF);
116
117
	reset();
118
119
	check_eq(memory_get_ram_bank(), 0, "a reset switches the machine to RAM bank 0");
120
	check_eq(memory_get_rom_bank(), 0, "and to ROM bank 0");
121
122
	check_divergent(read(RAM_BANK_REG) == 0,
123
	                "$0000 reports bank 0 after a reset",
124
	                "memory.c:110 clears the latch but the address reads RAM[0] "
125
	                "(memory.c:220), which the reset leaves alone, so the byte reports the "
126
	                "pre-reset bank while the machine is on bank 0");
127
	check_divergent(read(ROM_BANK_REG) == 0,
128
	                "$0001 reports bank 0 after a reset",
129
	                "same cause as $0000: the register's RAM shadow survives the reset");
130
}
131
132
// Memory Map, the address table:
133
//     |\$A000-\$BFFF|Banked RAM (8 KB window into one of 256 banks for a total of 2 MB)|
134
//
135
// One window, many banks: the same address in two banks is two different
136
// bytes, and switching back finds the first one again.
137
static void
138
test_the_ram_window_shows_one_bank_at_a_time(void)
139
{
140
	reset();
141
142
	write(RAM_BANK_REG, 0);
143
	write(BANKED_RAM_START, 0x11);
144
	write(BANKED_RAM_END, 0x22);
145
146
	write(RAM_BANK_REG, 1);
147
	write(BANKED_RAM_START, 0x33);
148
	write(BANKED_RAM_END, 0x44);
149
150
	check_eq(read(BANKED_RAM_START), 0x33, "bank 1 holds its own byte at the window start");
151
	check_eq(read(BANKED_RAM_END), 0x44, "and at the window end");
152
153
	write(RAM_BANK_REG, 0);
154
	check_eq(read(BANKED_RAM_START), 0x11, "and bank 0 still holds what it was given");
155
	check_eq(read(BANKED_RAM_END), 0x22, "at both ends of the window");
156
157
	// 8 KB: the byte below the window is fixed RAM and does not move with the
158
	// bank, which is what makes the window a window. $9EFF rather than $9FFF
159
	// because the Memory Map puts fixed RAM at "$0000-$9EFF" and I/O above it.
160
	write(0x9EFF, 0x55);
161
	write(RAM_BANK_REG, 1);
162
	check_eq(read(0x9EFF), 0x55, "the last byte of fixed RAM is not banked");
163
	check_eq(BANKED_RAM_END - BANKED_RAM_START + 1, 8192, "and the window is 8 KB");
164
}
165
166
// Memory Map:
167
//     |\$C000-\$FFFF|Banked System ROM and Cartridge ROM/RAM (16 KB window into one of 256 banks, see below)|
168
//     |$0001    |Current ROM/Cartridge bank (ROM is 0-31, Cartridge is 32-255) |
169
static void
170
test_the_rom_window_is_sixteen_kilobytes(void)
171
{
172
	reset();
173
	check_eq(0xFFFF - BANKED_ROM_START + 1, 16384, "the ROM window is 16 KB");
174
175
	// Banks 0-31 are the system ROM. With no cartridge attached the emulator
176
	// leaves 32-255 unmapped, which is the characterization group below.
177
	write(ROM_BANK_REG, 31);
178
	check_eq(memory_get_rom_bank(), 31, "the last system ROM bank can be selected");
179
}
180
181
// ─── Characterization: undocumented, pinned so a change is deliberate ───────
182
//
183
// ORACLE: none. The Memory Map chapter says of a 512 KB machine only that
184
// "(On systems with only 512 KB RAM, banks 64-255 are unavailable.)" and
185
// nowhere says what reading or writing one does. There is no schematic to
186
// consult. Everything below records this emulator's answer; none of it is a
187
// claim about what the hardware does.
188
//
189
// memory.c:262 calls it an open bus read and returns the high byte of the
190
// address, which is a plausible model of a floating 6502 data bus rather than
191
// a measured value.
192
193
static void
194
test_an_uninstalled_ram_bank_reads_as_the_address_high_byte(void)
195
{
196
	reset();
197
	check_eq(INSTALLED_RAM_BANKS, 64, "the fixture is a 512 KB machine");
198
199
	write(RAM_BANK_REG, INSTALLED_RAM_BANKS - 1);
200
	write(BANKED_RAM_START, 0x77);
201
	check_eq(read(BANKED_RAM_START), 0x77, "the last installed bank is real memory");
202
203
	write(RAM_BANK_REG, INSTALLED_RAM_BANKS);
204
	check_eq(read(BANKED_RAM_START), 0xA0, "the first uninstalled bank reads the address high byte");
205
	check_eq(read(BANKED_RAM_END), 0xBF, "which tracks the address across the window");
206
207
	write(RAM_BANK_REG, 0xFF);
208
	check_eq(read(BANKED_RAM_START), 0xA0, "and the highest bank reads the same");
209
}
210
211
static void
212
test_a_write_to_an_uninstalled_ram_bank_is_dropped(void)
213
{
214
	reset();
215
216
	write(RAM_BANK_REG, 0);
217
	write(BANKED_RAM_START, 0x5A);
218
219
	write(RAM_BANK_REG, INSTALLED_RAM_BANKS);
220
	write(BANKED_RAM_START, 0xA5);
221
222
	write(RAM_BANK_REG, 0);
223
	check_eq(read(BANKED_RAM_START), 0x5A, "a write to an uninstalled bank reaches no memory");
224
}
225
226
// memory.c:359-362 forwards writes to banks 32 and up to the cartridge and
227
// ignores the rest, so the system ROM is unwritable. The Hardware chapter
228
// describes a J1 jumper that makes the ROM flashable, which the emulator does
229
// not model in either direction.
230
static void
231
test_writes_to_system_rom_are_ignored(void)
232
{
233
	reset();
234
	write(ROM_BANK_REG, 0);
235
236
	const uint8_t before = read(BANKED_ROM_START);
237
	write(BANKED_ROM_START, (uint8_t)(before ^ 0xFF));
238
	check_eq(read(BANKED_ROM_START), before, "a write to system ROM changes nothing");
239
}
240
241
static void
242
test_a_rom_bank_above_the_system_rom_reads_as_open_bus(void)
243
{
244
	reset();
245
246
	// No cartridge is attached: fake_devices.c leaves CART null.
247
	write(ROM_BANK_REG, 32);
248
	check_eq(read(BANKED_ROM_START), 0xC0, "the first cartridge bank reads the address high byte");
249
	check_eq(read(0xFFFF), 0xFF, "which tracks the address across the window");
250
}
251
252
int
253
main(void)
254
{
255
	test_the_bank_registers_hold_and_report_a_bank();
256
	test_a_reset_returns_both_banks_to_zero();
257
	test_the_ram_window_shows_one_bank_at_a_time();
258
	test_the_rom_window_is_sixteen_kilobytes();
259
260
	test_an_uninstalled_ram_bank_reads_as_the_address_high_byte();
261
	test_a_write_to_an_uninstalled_ram_bank_is_dropped();
262
	test_writes_to_system_rom_are_ignored();
263
	test_a_rom_bank_above_the_system_rom_reads_as_open_bus();
264
265
	return x16_test_summary("memory_banking");
266
}