3Compare two VU1 .vsm files at the semantic level.
5Used as a CTest target in ps2gl to verify that openvcl produces the same
6*set* of operations as Sony's proprietary vcl for each VU1 renderer.
7Differences in pipe-pairing, register-allocator choices, and whitespace
8are intentionally ignored -- the goal is to surface real divergences
9(missing instructions, wrong opcodes, missing labels) and to track how
10close openvcl is getting to the reference as the dual-pipe scheduler
14 vsm_diff.py <reference.vsm> <openvcl.vsm>
17 0 = histograms and labels match.
18 1 = real divergence (different opcode set, different label set).
19 2 = file read / parse error.
21The script is intentionally permissive about pipe placement: only the
22opcode mix matters. A separate "instruction-count delta" line is printed
23to track scheduler progress over time.
28from collections
import Counter
31_DIRECTIVE_PREFIXES = (
".vu",
".align",
".global",
".name",
".end")
38_COMMENT_RE = re.compile(
r"^\s*;.*")
41_LABEL_RE = re.compile(
r"^\s*([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(?:;.*)?$")
50_PIPE_SPLIT_RE = re.compile(
r"\s{15,}")
55_FLAG_RE = re.compile(
r"^([a-z0-9.]+?)(\[[A-Za-z]+\])?$")
59 """Lowercase the mnemonic and strip any [E]/[I]/[D]/[T] flag suffix.
61 Keep dest fields (`.xyz`, `.w`) attached to the mnemonic so we can
62 distinguish `addi.xy` from `addi.xyz` -- they're semantically
63 different operations on different fields.
65 m = _FLAG_RE.match(tok.lower())
66 return m.group(1)
if m
else tok.lower()
70 """Return the flag suffix (e.g. "[E]") if present, else ""."""
71 m = _FLAG_RE.match(tok.lower())
72 return m.group(2)
or "" if m
else ""
76 """True iff `tok` looks like a VU1 mnemonic (as opposed to a register
77 or immediate operand).
79 Sony's reference output uses uppercase mnemonics with comma-joined
80 operands and no space between them; openvcl's output uses lowercase
81 mnemonics with space-separated operands. A consistent classifier
82 over both is to bucket each whitespace-separated token by what it
85 mnemonic: starts with a letter, is not a register name
86 register: V[FI]<digit>... or ACC[component] or single-letter I/Q/P/R
87 immediate: starts with a digit (incl. 0x...)
88 indirect: contains '(' (e.g. 62(VI00))
89 label-ref: trailing ':' -- handled before this function gets called
95 if not tok[0].isalpha():
105 if len(tok) > 2
and upper[:2]
in (
"VF",
"VI")
and tok[2].isdigit():
108 if upper.startswith(
"ACC"):
111 if upper
in (
"I",
"Q",
"P",
"R"):
117 """Return (opcode_histogram, flag_histogram, label_set, instr_count).
119 instr_count is the total number of pipe slots filled with anything
120 other than `nop` -- a rough "work-per-cycle" signal for the scheduler.
127 with open(path)
as f:
129 line = raw.rstrip(
"\n")
133 if _COMMENT_RE.match(line):
135 stripped = line.strip()
136 if stripped.startswith(_DIRECTIVE_PREFIXES):
139 label_match = _LABEL_RE.match(line)
141 labels.add(label_match.group(1))
149 halves = _PIPE_SPLIT_RE.split(line.strip(), maxsplit=1)
153 tok = half.split()[0]
164 return opcodes, flags, labels, instr_count
168 """Return dict {key: (a, b)} for keys where a and b disagree."""
170 for k
in sorted(set(a) | set(b)):
172 diffs[k] = (a[k], b[k])
176def main(argv) -> int:
178 print(f
"usage: {argv[0]} <reference.vsm> <openvcl.vsm>", file=sys.stderr)
181 ref_path, ovc_path = argv[1], argv[2]
184 ref_ops, ref_flags, ref_labels, ref_count =
parse_vsm(ref_path)
185 ovc_ops, ovc_flags, ovc_labels, ovc_count =
parse_vsm(ovc_path)
186 except FileNotFoundError
as e:
187 print(f
"missing file: {e.filename}", file=sys.stderr)
192 only_in_ref = ref_labels - ovc_labels
193 only_in_ovc = ovc_labels - ref_labels
195 histogram_ok =
not op_diff
196 flags_ok =
not flag_diff
197 labels_ok =
not (only_in_ref
or only_in_ovc)
202 ratio = float(
"inf")
if ovc_count
else 1.0
204 ratio = ovc_count / ref_count
206 print(f
"=== vsm_diff: {ref_path} vs {ovc_path}")
207 print(f
" non-nop slots: reference={ref_count} openvcl={ovc_count} ratio={ratio:.2f}")
208 print(f
" unique opcodes: reference={len(ref_ops)} openvcl={len(ovc_ops)}")
209 print(f
" labels: reference={len(ref_labels)} openvcl={len(ovc_labels)}")
210 print(f
" histogram_ok={histogram_ok} flags_ok={flags_ok} labels_ok={labels_ok}")
213 print(
" opcode count mismatches (op: reference -> openvcl):")
214 for op, (ra, oa)
in op_diff.items():
215 print(f
" {op:<14} {ra:>4} -> {oa}")
217 print(
" flag count mismatches ([X]: reference -> openvcl):")
218 for fl, (ra, oa)
in flag_diff.items():
219 print(f
" {fl:<6} {ra} -> {oa}")
221 print(
" labels only in reference:")
222 for l
in sorted(only_in_ref):
225 print(
" labels only in openvcl:")
226 for l
in sorted(only_in_ovc):
229 return 0
if (histogram_ok
and labels_ok)
else 1
232if __name__ ==
"__main__":
233 sys.exit(main(sys.argv))
str _normalize_mnemonic(str tok)
_diff_counters(Counter a, Counter b)
str _extract_flag(str tok)
bool _is_mnemonic_token(str tok)