OXYGENHUB
◆ main enhanced-x16-emulator / tests / check_vera_palette.py
4.0 KiB · Python · last changed chore(): initial commit 25 days ago source blame history raw
9120e874 Andy McCall 1
#!/usr/bin/env python3
2
"""Check the emulator's default palette against the one in VERA's bitstream.
3
4
VERA's palette RAM is initialised at configuration time from a Lattice memory
5
file, fpga/source/palette_ram.mem, baked into the FPGA bitstream. That makes
6
the default palette a property of the hardware rather than a convention the
7
ROM establishes, so src/video.c's default_palette[] is checkable against it
8
entry for entry.
9
10
    python tests/check_vera_palette.py
11
12
Skips when the RTL has not been fetched. Nothing in the suite requires the
13
reference to be present -- run tests/fetch_vera_rtl.py to get it. CI does
14
fetch it, and sets X16_VERA_RTL_REQUIRED so that a broken fetch is a failure
15
rather than a silent skip, the same way X16_KLAUS_REQUIRED works.
16
17
WHY THIS IS A SCRIPT AND NOT A C TEST
18
-------------------------------------
19
The palette the renderer uses lives in a static array with no accessor, and
20
the only path to it is rendered pixels, which need the framebuffer. Comparing
21
the two declarations needs neither. It also catches an entry being edited by
22
hand, which is the way a 256-entry table actually goes wrong.
23
24
WHAT THIS DOES NOT CHECK
25
------------------------
26
How a 4-bit channel becomes an 8-bit one. video.c:792-794 replicates the
27
nibble, so 0xF becomes 0xFF. The RTL cannot settle that: video_vga.v declares
28
vga_r, vga_g and vga_b as [3:0] and assigns palette_rgb_data[11:8] etc.
29
straight to them, so the hardware terminates at four bits per channel and the
30
expansion is the emulator's own presentation choice. Asserting it against the
31
Verilog would be inventing an oracle.
32
"""
33
34
import os
35
import re
36
import sys
37
from pathlib import Path
38
39
ROOT = Path(__file__).resolve().parent.parent
40
VIDEO_C = ROOT / "src" / "video.c"
41
MEM = ROOT / "tests" / "vera-rtl" / "palette_ram.mem"
42
43
ENTRIES = 256
44
45
46
def required():
47
    v = os.environ.get("X16_VERA_RTL_REQUIRED")
48
    return v is not None and v not in ("", "0")
49
50
51
def emulator_palette():
52
    src = VIDEO_C.read_text(encoding="utf-8", errors="replace")
53
    m = re.search(r"default_palette\[\]\s*=\s*\{(.*?)\}\s*;", src, re.S)
54
    if not m:
55
        print(f"FAIL: default_palette[] not found in {VIDEO_C}")
56
        return None
57
    return [int(v, 16) for v in re.findall(r"0x([0-9a-fA-F]+)", m.group(1))]
58
59
60
def rtl_palette():
61
    out = []
62
    for lineno, line in enumerate(MEM.read_text(encoding="utf-8").splitlines(), 1):
63
        line = line.strip()
64
        if not line:
65
            continue
66
        try:
67
            out.append(int(line, 16))
68
        except ValueError:
69
            print(f"FAIL: {MEM.name}:{lineno} is not a hex word: {line!r}")
70
            return None
71
    return out
72
73
74
def main():
75
    if not MEM.exists():
76
        if required():
77
            print(f"FAIL: {MEM.name} not present and X16_VERA_RTL_REQUIRED is set")
78
            print("      Run tests/fetch_vera_rtl.py. A skip here would leave the")
79
            print("      default palette unchecked on a green run.")
80
            return 1
81
        print(f"skip: {MEM.name} not present (run tests/fetch_vera_rtl.py)")
82
        return 0
83
84
    emu = emulator_palette()
85
    rtl = rtl_palette()
86
    if emu is None or rtl is None:
87
        return 1
88
89
    ok = True
90
91
    # Counted separately from the comparison. Two tables of different lengths
92
    # would otherwise compare equal over the shorter one and say nothing about
93
    # the rest.
94
    for name, got in (("video.c", len(emu)), (MEM.name, len(rtl))):
95
        if got != ENTRIES:
96
            print(f"FAIL: {name} holds {got} entries, expected {ENTRIES}")
97
            ok = False
98
    if not ok:
99
        return 1
100
101
    mismatches = 0
102
    for i, (a, b) in enumerate(zip(emu, rtl)):
103
        if a != b:
104
            mismatches += 1
105
            if mismatches <= 8:
106
                print(f"FAIL: entry {i} ($1FA{i * 2:02X}): video.c 0x{a:03x}, RTL 0x{b:03x}")
107
    if mismatches > 8:
108
        print(f"      and {mismatches - 8} more")
109
110
    if mismatches:
111
        print(f"FAIL: {mismatches} of {ENTRIES} palette entries differ")
112
        return 1
113
114
    print(f"ok  : all {ENTRIES} default palette entries match palette_ram.mem")
115
    return 0
116
117
118
if __name__ == "__main__":
119
    sys.exit(main())