OXYGENHUB
◆ main enhanced-x16-emulator / tests / test_vera_regbank.c
7.5 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
1
// VERA register banking: CTRL at $9F25, and the four registers it banks.
2
//
3
// ORACLE (strength B): the VERA Verilog.
4
//
5
//   repo:   https://github.com/X16Community/vera-module
6
//   tag:    v47.0.2   (commit 45cc1f05)
7
//   file:   fpga/source/top.v
8
//
9
// VERA has 32 register addresses and far more than 32 registers, so $9F29-$9F2C
10
// mean different things depending on DCSEL. Get the banking wrong and a write
11
// lands in an unrelated register -- a scroll position becomes a border colour
12
// -- which is the kind of fault that shows up as a corrupted display three
13
// subsystems away from the cause.
14
//
15
// See tests/support/video_fixture.c for the link seam.
16
17
#include "support/video_fixture.h"
18
#include "support/harness.h"
19
20
#include "video.h"
21
22
#include <string.h>
23
24
#define VERA_CTRL    0x05
25
#define VERA_DC_0    0x09
26
#define VERA_DC_1    0x0A
27
#define VERA_DC_2    0x0B
28
#define VERA_DC_3    0x0C
29
30
// top.v:334-337   if (do_write && access_addr == 5'h05) begin
31
//                     fpga_reconfigure_next = write_data[7];
32
//                     dc_select_next        = write_data[6:1];
33
//                     vram_addr_select_next = write_data[0];
34
static uint8_t
35
ctrl(unsigned dcsel, unsigned addrsel)
36
{
37
	return (uint8_t)(((dcsel & 0x3F) << 1) | (addrsel & 1));
38
}
39
40
static void
41
select_dcsel(unsigned dcsel)
42
{
43
	video_write(VERA_CTRL, ctrl(dcsel, 0));
44
}
45
46
// top.v:86    reg [5:0] dc_select_r, dc_select_next;
47
// top.v:336   dc_select_next = write_data[6:1];
48
//
49
// Six bits, so 64 banks -- not the four or eight a reader might assume from the
50
// registers actually in use.
51
static void
52
test_dcsel_is_six_bits(void)
53
{
54
	video_reset();
55
56
	select_dcsel(0);
57
	check_eq(video_read(VERA_CTRL, true) >> 1 & 0x3F, 0u, "DCSEL 0 reads back");
58
59
	select_dcsel(63);
60
	check_eq(video_read(VERA_CTRL, true) >> 1 & 0x3F, 63u,
61
	         "DCSEL 63 reads back, so the field is six bits wide");
62
63
	select_dcsel(0x2A);
64
	check_eq(video_read(VERA_CTRL, true) >> 1 & 0x3F, 0x2Au, "and an arbitrary value survives");
65
}
66
67
// top.v:173   5'h05: rddata = {1'b0, dc_select_r, vram_addr_select_r};
68
//
69
// Bit 7 reads back as zero regardless of what was written. On a write it is
70
// fpga_reconfigure (top.v:335), a command rather than stored state, so there is
71
// nothing to read back.
72
static void
73
test_ctrl_bit7_reads_as_zero(void)
74
{
75
	video_reset();
76
77
	video_write(VERA_CTRL, 0x80);
78
	check_eq(video_read(VERA_CTRL, true) & 0x80, 0u,
79
	         "bit 7 reads back as zero after being written high");
80
81
	video_write(VERA_CTRL, 0xFF);
82
	check_eq(video_read(VERA_CTRL, true) & 0x80, 0u, "and still zero with every bit set");
83
	check_eq(video_read(VERA_CTRL, true) & 0x7F, 0x7Fu,
84
	         "while DCSEL and ADDRSEL keep what was written");
85
}
86
87
// top.v:337   vram_addr_select_next = write_data[0];
88
//
89
// ADDRSEL shares the register with DCSEL, so changing one must not disturb the
90
// other. Both are written by the same byte, and software sets them separately.
91
static void
92
test_addrsel_and_dcsel_are_independent(void)
93
{
94
	video_reset();
95
96
	video_write(VERA_CTRL, ctrl(5, 1));
97
	uint8_t got = video_read(VERA_CTRL, true);
98
	check_eq(got & 1, 1u, "ADDRSEL is bit 0");
99
	check_eq((got >> 1) & 0x3F, 5u, "and DCSEL is unaffected by it");
100
101
	video_write(VERA_CTRL, ctrl(5, 0));
102
	got = video_read(VERA_CTRL, true);
103
	check_eq(got & 1, 0u, "ADDRSEL clears");
104
	check_eq((got >> 1) & 0x3F, 5u, "and DCSEL still holds");
105
}
106
107
// top.v:179-208   5'h09..5'h0C each switch on dc_select_r
108
//
109
// The same four addresses carry different registers per bank. This writes a
110
// value in one bank and checks it is still there after a round trip through
111
// another, which is what a program does when it touches scroll and scale in the
112
// same frame.
113
static void
114
test_banked_registers_do_not_alias(void)
115
{
116
	video_reset();
117
118
	// DCSEL 0: $9F2A is DC_HSCALE, $9F2C is DC_BORDER.
119
	select_dcsel(0);
120
	video_write(VERA_DC_1, 0x40);
121
	video_write(VERA_DC_3, 0x11);
122
123
	// DCSEL 1: the same addresses are the active-area window.
124
	select_dcsel(1);
125
	video_write(VERA_DC_1, 0x99);
126
	video_write(VERA_DC_3, 0x77);
127
128
	select_dcsel(0);
129
	check_eq(video_read(VERA_DC_1, true), 0x40u, "bank 0 kept its own value");
130
	check_eq(video_read(VERA_DC_3, true), 0x11u, "and so did its other register");
131
132
	select_dcsel(1);
133
	check_eq(video_read(VERA_DC_1, true), 0x99u, "bank 1 kept its own value");
134
	check_eq(video_read(VERA_DC_3, true), 0x77u, "and so did its other register");
135
}
136
137
// top.v:184   default: rddata = "V";
138
// top.v:191   default: rddata = 8'd47;
139
// top.v:199   default: rddata = 8'd0;
140
// top.v:207   default: rddata = 8'd0;
141
//
142
// Banks with nothing mapped do not read as zero or as open bus: they return the
143
// version string, which is how software identifies the chip. DCSEL 3 has
144
// nothing mapped at any of the four addresses.
145
//
146
// The last byte diverges. The RTL in tag v47.0.2 reports 47.0.0 -- its version
147
// registers were never bumped to match the tag name -- while this emulator
148
// reports 47.0.2 from VERA_VERSION_PATCH in video.c. v48.0.1 introduced
149
// VERSION_MAJOR/MINOR/BUILD defines and is self-consistent, so no revision
150
// actually reports 47.0.2.
151
static void
152
test_unmapped_banks_return_the_version(void)
153
{
154
	video_reset();
155
	select_dcsel(3);
156
157
	// Real reads, not debug reads: video.c:2690 diverts a debug read to the
158
	// stored composer array before the version path is reached.
159
	check_eq(video_read(VERA_DC_0, false), (uint32_t)'V',
160
	         "an unmapped bank reports 'V' at the first address");
161
	check_eq(video_read(VERA_DC_1, false), 47u, "the major version at the second");
162
	check_eq(video_read(VERA_DC_2, false), 0u, "the minor version at the third");
163
164
	check_divergent(video_read(VERA_DC_3, false) == 0,
165
	                "the patch version at the fourth reads 0",
166
	                "emulator reports 2 from VERA_VERSION_PATCH; the RTL in tag v47.0.2 "
167
	                "reports 47.0.0, so no revision reports 47.0.2");
168
}
169
170
// video.c:2690   if (debugOn) return video_get_dc_value(i);
171
//
172
// The debugger reads these registers by a different route than the machine
173
// does, and for an unmapped bank the two disagree: the CPU sees the version
174
// string, the debugger sees the stored composer bytes. Nothing in the RTL
175
// corresponds to a debug read, so this is not a hardware divergence -- it is
176
// the debugger showing something the running machine would never see, which is
177
// worth knowing before trusting a register panel.
178
//
179
// The debugOn contract elsewhere is about side effects. This is about the value
180
// itself, which that contract does not cover.
181
static void
182
test_debug_and_real_reads_disagree_on_unmapped_banks(void)
183
{
184
	video_reset();
185
	select_dcsel(3);
186
187
	check(video_read(VERA_DC_0, true) != video_read(VERA_DC_0, false),
188
	      "a debug read of an unmapped bank does not match what the CPU sees");
189
	check_eq(video_read(VERA_DC_0, true), 0u, "the debugger sees the stored composer byte");
190
	check_eq(video_read(VERA_DC_0, false), (uint32_t)'V', "while the CPU sees the version");
191
}
192
193
// Reading the version does not depend on having selected a bank first, and does
194
// not disturb the selection: a program probes the chip and carries on.
195
static void
196
test_reading_the_version_leaves_the_bank_alone(void)
197
{
198
	video_reset();
199
	select_dcsel(3);
200
201
	video_read(VERA_DC_0, true);
202
	video_read(VERA_DC_1, true);
203
204
	check_eq(video_read(VERA_CTRL, true) >> 1 & 0x3F, 3u,
205
	         "probing the version leaves DCSEL where it was");
206
}
207
208
int
209
main(void)
210
{
211
	test_dcsel_is_six_bits();
212
	test_ctrl_bit7_reads_as_zero();
213
	test_addrsel_and_dcsel_are_independent();
214
	test_banked_registers_do_not_alias();
215
	test_unmapped_banks_return_the_version();
216
	test_debug_and_real_reads_disagree_on_unmapped_banks();
217
	test_reading_the_version_leaves_the_bank_alone();
218
	return x16_test_summary("vera_regbank");
219
}