OXYGENHUB
◆ main enhanced-x16-emulator / tests / test_vera_video.c
6.6 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
9120e874 Andy McCall 1
// VERA address registers and the data ports at $9F20-$9F24.
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
//   files:  fpga/source/addr_data.v, fpga/source/top.v
8
//
9
// This is the first test to link the real video.c. That file is the VERA
10
// implementation and the emulator's SDL host in one, and only the first half is
11
// under test here -- see tests/support/video_fixture.c for the seam. No test
12
// calls video_init(), video_update() or video_end(); video_reset() is
13
// deterministic and is called between cases.
14
//
15
// The data ports are where nearly every VERA access goes: a program sets an
16
// address and an auto-increment once, then reads or writes $9F23 in a loop. The
17
// increment arithmetic is therefore load-bearing for all of it, and it is also
18
// the part a debugger can disturb, because reading the data port MOVES the
19
// address on real hardware.
20
21
#include "support/video_fixture.h"
22
#include "support/harness.h"
23
24
#include "video.h"
25
26
#include <string.h>
27
28
#define VERA_ADDR_L  0x00
29
#define VERA_ADDR_M  0x01
30
#define VERA_ADDR_H  0x02
31
#define VERA_DATA0   0x03
32
#define VERA_DATA1   0x04
33
#define VERA_CTRL    0x05
34
35
// video.c:2792-2796   case 0x02:
36
//                         io_addr[io_addrsel] = ... | ((value & 0x1) << 16);
37
//                         fx_nibble_bit[io_addrsel]  = (value >> 1) & 0x1;
38
//                         fx_nibble_incr[io_addrsel] = (value >> 2) & 0x1;
39
//                         io_inc[io_addrsel]         = value >> 3;
40
//
41
// So ADDRx_H carries address bit 16 in bit 0, two nibble-mode controls in bits
42
// 1 and 2, the decrement flag in bit 3 and the increment code in bits 7:4.
43
static uint8_t
44
addr_h(unsigned incr_code, bool decrement, unsigned addr_bit16)
45
{
46
	return (uint8_t)((incr_code << 4) | ((decrement ? 1u : 0u) << 3) | (addr_bit16 & 1u));
47
}
48
49
static void
50
set_address(uint8_t sel, uint32_t addr, unsigned incr_code, bool decrement)
51
{
52
	video_write(VERA_CTRL, sel & 1);
53
	video_write(VERA_ADDR_L, addr & 0xFF);
54
	video_write(VERA_ADDR_M, (addr >> 8) & 0xFF);
55
	video_write(VERA_ADDR_H, addr_h(incr_code, decrement, addr >> 16));
56
}
57
58
// addr_data.v:182-213, the whole increment decode. Indexed there as
59
// {decr, incr}; the emulator packs the same two fields as (incr << 1) | decr
60
// and interleaves the signs in its table, which comes to the same thing.
61
//
62
// The four values that are not powers of two -- 40, 80, 160, 320, 640 -- are
63
// the ones worth having in a test: they exist so a program can step whole tile
64
// rows and character cells, and they are exactly what a "tidied" implementation
65
// would get wrong by assuming the series is 1 << n.
66
static const int expected_increment[16] = {
67
	0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 40, 80, 160, 320, 640
68
};
69
70
static void
71
test_reset_clears_the_address_registers(void)
72
{
73
	video_reset();
74
	check_eq(video_get_address(0), 0u, "reset clears the first address register");
75
	check_eq(video_get_address(1), 0u, "reset clears the second");
76
}
77
78
// addr_data.v:183-197 for the increments, :199-213 for the decrements.
79
static void
80
test_every_increment_code(void)
81
{
82
	for (unsigned code = 0; code < 16; code++) {
83
		video_reset();
84
		set_address(0, 0x1000, code, false);
85
		video_write(VERA_DATA0, 0xAA);
86
		check_eq(video_get_address(0), 0x1000u + expected_increment[code],
87
		         "the increment code advances the address by its documented step");
88
	}
89
90
	for (unsigned code = 0; code < 16; code++) {
91
		video_reset();
92
		set_address(0, 0x1000, code, true);
93
		video_write(VERA_DATA0, 0xAA);
94
		check_eq(video_get_address(0), 0x1000u - expected_increment[code],
95
		         "and the decrement flag reverses it");
96
	}
97
}
98
99
// top.v:169-170 carries vram_addr_0_r[16] through the register interface, so
100
// the address is 17 bits and wraps at 128K rather than saturating.
101
static void
102
test_address_wraps_at_128k(void)
103
{
104
	video_reset();
105
	set_address(0, 0x1FFFF, 1, false);   // one byte short of the end
106
	video_write(VERA_DATA0, 0x55);
107
	check_eq(video_get_address(0), 0u, "incrementing past the end wraps to zero");
108
109
	video_reset();
110
	set_address(0, 0x00000, 1, true);
111
	video_write(VERA_DATA0, 0x55);
112
	check_eq(video_get_address(0), 0x1FFFFu, "and decrementing below zero wraps to the top");
113
}
114
115
// addr_data.v:75-78 exposes two independent address registers with their own
116
// increments, and top.v:169-170 selects between them. A program relies on that
117
// to stream from one place while writing to another.
118
static void
119
test_the_two_address_registers_are_independent(void)
120
{
121
	video_reset();
122
	set_address(0, 0x0100, 1, false);    // step by 1
123
	set_address(1, 0x0200, 2, false);    // step by 2
124
125
	video_write(VERA_CTRL, 0);
126
	video_write(VERA_DATA0, 0x11);
127
	check_eq(video_get_address(0), 0x0101u, "the first register uses its own increment");
128
	check_eq(video_get_address(1), 0x0200u, "and leaves the second alone");
129
130
	video_write(VERA_CTRL, 1);
131
	video_write(VERA_DATA1, 0x22);
132
	check_eq(video_get_address(1), 0x0202u, "the second uses its own");
133
	check_eq(video_get_address(0), 0x0101u, "and leaves the first alone");
134
}
135
136
// The data port is not idempotent: a read advances the address exactly as a
137
// write does. This is the behaviour a debugger most easily breaks, by peeking
138
// VRAM through the same path the machine uses -- so it is pinned here, and the
139
// debug-read path is pinned separately in the debugOn contract tests.
140
static void
141
test_reading_the_data_port_advances_the_address(void)
142
{
143
	video_reset();
144
	set_address(0, 0x0040, 1, false);
145
146
	video_read(VERA_DATA0, false);
147
	check_eq(video_get_address(0), 0x0041u, "a real read advances the address");
148
149
	video_read(VERA_DATA0, false);
150
	check_eq(video_get_address(0), 0x0042u, "and again on the next read");
151
}
152
153
// Round-trip through VRAM: write a run of bytes with auto-increment, then read
154
// them back the same way. Proves the address arithmetic and the memory it
155
// indexes agree, which the increment tests alone do not.
156
static void
157
test_data_port_round_trip(void)
158
{
159
	video_reset();
160
161
	set_address(0, 0x0400, 1, false);
162
	for (unsigned i = 0; i < 16; i++) {
163
		video_write(VERA_DATA0, (uint8_t)(0xF0 + i));
164
	}
165
	check_eq(video_get_address(0), 0x0410u, "sixteen writes advanced sixteen bytes");
166
167
	set_address(0, 0x0400, 1, false);
168
	bool ok = true;
169
	for (unsigned i = 0; i < 16; i++) {
170
		if (video_read(VERA_DATA0, false) != (uint8_t)(0xF0 + i)) {
171
			ok = false;
172
		}
173
	}
174
	check(ok, "the bytes read back in the order they were written");
175
}
176
177
int
178
main(void)
179
{
180
	test_reset_clears_the_address_registers();
181
	test_every_increment_code();
182
	test_address_wraps_at_128k();
183
	test_the_two_address_registers_are_independent();
184
	test_reading_the_data_port_advances_the_address();
185
	test_data_port_round_trip();
186
	return x16_test_summary("vera_video");
187
}