OXYGENHUB
# 2980b7b6d7cd enhanced-x16-emulator / tests / test_processor_tests.c
20.4 KiB · C · last changed chore(): initial commit 26 days ago source blame history raw
9120e874 Andy McCall 1
// Conformance against SingleStepTests/ProcessorTests: every opcode, thousands
2
// of randomised starting states each.
3
//
4
// Three claims are checked per case, counted separately because they fail
5
// independently:
6
//
7
//   1. final register and memory state;
8
//   2. total cycle count;
9
//   3. the bus trace -- which addresses were touched, in what order, including
10
//      the dummy accesses real hardware makes.
11
//
12
// The core cannot pass (3) as written: it adds an instruction's cycles in one
13
// go rather than one bus access per cycle, and skips dummy accesses. All three
14
// are therefore measured against a recorded baseline rather than asserted
15
// against hardware.
16
//
17
// Fixtures come from tests/fetch_pt.py and are not committed. Without them
18
// this reports skipped.
19
20
#include "support/cpu_fixture.h"
21
#include "support/harness.h"
22
23
#include <stdio.h>
24
#include <stdlib.h>
25
#include <string.h>
26
27
#define MAX_RAM    64
28
// MVP and MVN move a block, and the suite records the whole move as one
29
// instruction: 100 cycles in the 65C816 emulation-mode set.
30
#define MAX_CYCLES 160
31
32
// Cycle flags, as written by pt_convert.py.
33
#define PTF_VDA   0x01
34
#define PTF_VPA   0x02
35
#define PTF_VPB   0x04
36
#define PTF_WRITE 0x08
37
38
#define PT_CPU_65C02 0
39
#define PT_CPU_65816 1
40
41
typedef struct {
42
	uint16_t pc, s, a, x, y, d;
43
	uint8_t  p, e, dbr, pbr;
44
} pt_state;
45
46
typedef struct {
47
	uint32_t addr;
48
	uint8_t  value;
49
	uint8_t  flags;
50
} pt_cycle;
51
52
typedef struct {
53
	uint8_t  opcode, cpu;
54
	pt_state initial, final_;
55
	uint16_t init_ram_n, final_ram_n, cycle_n;
56
	struct { uint32_t addr; uint8_t value; } init_ram[MAX_RAM], final_ram[MAX_RAM];
57
	pt_cycle cycles[MAX_CYCLES];
58
} pt_case;
59
60
// ---- Bus log, filled by the fixture's hooks -------------------------------
61
62
#define BUS_MAX 160
63
static pt_cycle bus[BUS_MAX];
64
static int      bus_n;
65
66
static void
67
note_read(uint32_t addr, uint8_t value)
68
{
69
	if (bus_n < BUS_MAX) {
70
		bus[bus_n].addr = addr;
71
		bus[bus_n].value = value;
72
		bus[bus_n].flags = 0;
73
		bus_n++;
74
	}
75
}
76
77
static void
78
note_write(uint32_t addr, uint8_t value)
79
{
80
	if (bus_n < BUS_MAX) {
81
		bus[bus_n].addr = addr;
82
		bus[bus_n].value = value;
83
		bus[bus_n].flags = PTF_WRITE;
84
		bus_n++;
85
	}
86
}
87
88
static bool
89
read_exact(FILE *f, void *dst, size_t n)
90
{
91
	return fread(dst, 1, n, f) == n;
92
}
93
94
static bool
95
read_state(FILE *f, pt_state *s)
96
{
97
	return read_exact(f, &s->pc, 2) && read_exact(f, &s->s, 2) &&
98
	       read_exact(f, &s->a, 2) && read_exact(f, &s->x, 2) &&
99
	       read_exact(f, &s->y, 2) && read_exact(f, &s->p, 1) &&
100
	       read_exact(f, &s->e, 1) && read_exact(f, &s->dbr, 1) &&
101
	       read_exact(f, &s->pbr, 1) && read_exact(f, &s->d, 2);
102
}
103
104
static bool
105
read_addr24(FILE *f, uint32_t *out)
106
{
107
	uint32_t v = 0;
108
	if (!read_exact(f, &v, 4)) return false;
109
	*out = v & 0xFFFFFF;
110
	return true;
111
}
112
113
// Why read_case() last returned false. A truncated fixture and a case too large
114
// for these buffers look identical otherwise, and the loop just stops early.
115
static const char *read_error = NULL;
116
117
static bool
118
read_case(FILE *f, pt_case *c)
119
{
120
	if (!read_exact(f, &c->opcode, 1)) return false;  // clean end of file
121
	if (!read_exact(f, &c->cpu, 1)) { read_error = "truncated"; return false; }
122
	if (!read_state(f, &c->initial)) { read_error = "truncated"; return false; }
123
	if (!read_exact(f, &c->init_ram_n, 2)) { read_error = "truncated"; return false; }
124
	if (c->init_ram_n > MAX_RAM) { read_error = "initial ram too large"; return false; }
125
	for (int i = 0; i < c->init_ram_n; i++) {
126
		if (!read_addr24(f, &c->init_ram[i].addr)) return false;
127
		if (!read_exact(f, &c->init_ram[i].value, 1)) return false;
128
	}
129
	if (!read_state(f, &c->final_)) { read_error = "truncated"; return false; }
130
	if (!read_exact(f, &c->final_ram_n, 2)) { read_error = "truncated"; return false; }
131
	if (c->final_ram_n > MAX_RAM) { read_error = "final ram too large"; return false; }
132
	for (int i = 0; i < c->final_ram_n; i++) {
133
		if (!read_addr24(f, &c->final_ram[i].addr)) return false;
134
		if (!read_exact(f, &c->final_ram[i].value, 1)) return false;
135
	}
136
	if (!read_exact(f, &c->cycle_n, 2)) { read_error = "truncated"; return false; }
137
	if (c->cycle_n > MAX_CYCLES) { read_error = "too many cycles"; return false; }
138
	for (int i = 0; i < c->cycle_n; i++) {
139
		if (!read_addr24(f, &c->cycles[i].addr)) return false;
140
		if (!read_exact(f, &c->cycles[i].value, 1)) return false;
141
		if (!read_exact(f, &c->cycles[i].flags, 1)) return false;
142
	}
143
	return true;
144
}
145
146
// ---- Running one case ------------------------------------------------------
147
148
typedef struct {
149
	long total, state_ok, cycles_ok, bus_ok;
150
	long state_ok_ignoring_b;  // matches but for bit 4 of P
151
	uint8_t first_bad_opcode;
152
	bool    have_first_bad;
153
	char    first_bad[256];
154
} pt_result;
155
156
// Per opcode, so a report names what to fix rather than just how much is wrong.
157
typedef struct {
158
	long total, state_ok, state_ok_ignoring_b, cycles_ok, bus_ok;
159
	char sample[192];   // first state mismatch seen for this opcode
160
	bool have_sample;
161
	bool not_comparable;
162
} pt_opcode;
163
164
static pt_opcode by_opcode[256];
165
166
// Native mode picks the accumulator and index widths independently, so a fault
167
// in one width looks like scattered opcode failures until the results are split
168
// this way.
169
enum { MODE_EMU, MODE_M8X8, MODE_M8X16, MODE_M16X8, MODE_M16X16, MODE_COUNT };
170
171
static const char *mode_name[MODE_COUNT] = {
172
	"emulation", "native M8 X8", "native M8 X16", "native M16 X8",
173
	"native M16 X16",
174
};
175
176
static struct {
177
	long total, state_ok, cycles_ok;
178
} by_mode[MODE_COUNT];
179
180
static int
181
mode_of(const pt_case *c)
182
{
183
	if (c->cpu != PT_CPU_65816 || c->initial.e) {
184
		return MODE_EMU;
185
	}
186
	bool m8 = (c->initial.p & FLAG_MEMORY_WIDTH) != 0;
187
	bool x8 = (c->initial.p & FLAG_INDEX_WIDTH) != 0;
188
	if (m8) {
189
		return x8 ? MODE_M8X8 : MODE_M8X16;
190
	}
191
	return x8 ? MODE_M16X8 : MODE_M16X16;
192
}
193
194
// Opcodes the suite records in a way that cannot be compared against a single
195
// step of this emulator. Counted separately rather than left to read as
196
// failure, since none of them is a fault in the core.
197
//
198
//   $44 MVP, $54 MVN   Move one byte per execution and rewind PC so the
199
//                      instruction runs again, which is what makes a block move
200
//                      interruptible. The suite records a fixed 100-cycle
201
//                      prefix of the whole move -- fourteen iterations of a
202
//                      transfer that may run for thousands.
203
//
204
//   $CB WAI            Halts until an interrupt arrives. Both WDC datasheets
205
//                      give three cycles; the suite records a fourth from the
206
//                      wait state, which on hardware continues indefinitely.
207
//
208
//   $DB                STP on a stock 65C816. This emulator maps it to a
209
//                      debugger trap instead, which is a deliberate difference
210
//                      rather than a timing error.
211
static bool
212
is_not_comparable(const pt_case *c)
213
{
214
	if (c->cpu != PT_CPU_65816) {
215
		return false;
216
	}
217
	switch (c->opcode) {
218
		case 0x44: case 0x54: case 0xCB: case 0xDB:
219
			return true;
220
		default:
221
			return false;
222
	}
223
}
224
225
static void
226
run_case(const pt_case *c, pt_result *r)
227
{
228
	const bool is816 = (c->cpu == PT_CPU_65816);
229
230
	// The suite chooses the mode per case, through the E bit and the width
231
	// flags in P, so reset into the plainest configuration for the core and
232
	// let the case's own registers select the rest.
233
	cpu_reset_to(is816 ? CPU_816_EMU : CPU_65C02, 0x0000);
234
	for (int i = 0; i < c->init_ram_n; i++) {
235
		cpu_seed(c->init_ram[i].addr, c->init_ram[i].value);
236
	}
237
	regs.pc     = c->initial.pc;
238
	regs.status = c->initial.p;
239
240
	if (is816) {
241
		regs.e  = c->initial.e;
242
		regs.sp = c->initial.s;
243
		regs.c  = c->initial.a;
244
		regs.x  = c->initial.x;
245
		regs.y  = c->initial.y;
246
		regs.dp = c->initial.d;
247
		regs.db = c->initial.dbr;
248
		regs.k  = c->initial.pbr;
249
		// Emulation mode pins the stack to page one and forces both widths;
250
		// the suite supplies a full 16-bit S even there.
251
		if (regs.e) {
252
			regs.sp = 0x0100 | (c->initial.s & 0xFF);
253
			regs.status |= FLAG_INDEX_WIDTH | FLAG_MEMORY_WIDTH;
254
		}
255
	} else {
256
		regs.sp = 0x0100 | (c->initial.s & 0xFF);
257
		regs.a  = (uint8_t)c->initial.a;
258
		regs.xl = (uint8_t)c->initial.x;
259
		regs.yl = (uint8_t)c->initial.y;
260
	}
261
262
	bus_n = 0;
263
	cpu_bus_read_hook = note_read;
264
	cpu_bus_write_hook = note_write;
265
	uint32_t spent = cpu_step();
266
	cpu_bus_read_hook = NULL;
267
	cpu_bus_write_hook = NULL;
268
269
	r->total++;
270
	pt_opcode *op = &by_opcode[c->opcode];
271
	op->total++;
272
	op->not_comparable = is_not_comparable(c);
273
	const int mode = mode_of(c);
274
	by_mode[mode].total++;
275
276
	// Compare at the width the case ran in. Reading regs.a in a 16-bit case
277
	// would drop the high byte and report a false pass.
278
	uint16_t got_a = is816 ? regs.c : (uint16_t)regs.a;
279
	uint16_t got_x = is816 ? regs.x : (uint16_t)regs.xl;
280
	uint16_t got_y = is816 ? regs.y : (uint16_t)regs.yl;
281
	uint16_t got_s = is816 && !regs.e ? regs.sp
282
	                                  : (uint16_t)(regs.sp & 0xFF);
283
	uint16_t want_s = is816 && !c->final_.e ? c->final_.s
284
	                                        : (uint16_t)(c->final_.s & 0xFF);
285
286
	bool regs_but_p = regs.pc == c->final_.pc &&
287
	                  got_s == want_s &&
288
	                  got_a == c->final_.a &&
289
	                  got_x == c->final_.x &&
290
	                  got_y == c->final_.y;
291
	if (is816) {
292
		regs_but_p = regs_but_p && regs.dp == c->final_.d &&
293
		             regs.db == c->final_.dbr && regs.k == c->final_.pbr &&
294
		             regs.e == c->final_.e;
295
	}
296
	bool ram_ok = true;
297
	for (int i = 0; i < c->final_ram_n; i++) {
298
		if (cpu_mem[c->final_ram[i].addr] != c->final_ram[i].value) {
299
			ram_ok = false;
300
		}
301
	}
302
303
	bool state = regs_but_p && ram_ok && regs.status == c->final_.p;
304
	// Bit 4 has no physical existence: it only means anything in a copy of P
305
	// pushed to the stack. Counting matches that ignore it separates one
306
	// systematic disagreement from genuinely wrong results.
307
	bool state_but_b = regs_but_p && ram_ok &&
308
	                   (regs.status & ~0x10) == (c->final_.p & ~0x10);
309
310
	if (state) {
311
		r->state_ok++;
312
		op->state_ok++;
313
		by_mode[mode].state_ok++;
314
	}
315
	if (state_but_b) {
316
		r->state_ok_ignoring_b++;
317
		op->state_ok_ignoring_b++;
318
	}
319
	if (!state_but_b && !op->have_sample) {
320
		op->have_sample = true;
321
		snprintf(op->sample, sizeof op->sample,
322
		         "PC $%04X/$%04X A $%04X/$%04X X $%04X/$%04X "
323
		         "Y $%04X/$%04X P $%02X/$%02X S $%04X/$%04X D $%04X/$%04X "
324
		         "DBR $%02X/$%02X PBR $%02X/$%02X E %d/%d (got/want)",
325
		         regs.pc, c->final_.pc, got_a, c->final_.a,
326
		         got_x, c->final_.x, got_y, c->final_.y,
327
		         regs.status, c->final_.p, got_s, want_s,
328
		         regs.dp, c->final_.d, regs.db, c->final_.dbr,
329
		         regs.k, c->final_.pbr, regs.e, c->final_.e);
330
	}
331
	if (!state && !r->have_first_bad) {
332
		r->have_first_bad = true;
333
		r->first_bad_opcode = c->opcode;
334
		snprintf(r->first_bad, sizeof r->first_bad,
335
		         "opcode $%02X: PC $%04X/$%04X A $%04X/$%04X X $%04X/$%04X "
336
		         "Y $%04X/$%04X P $%02X/$%02X S $%04X/$%04X",
337
		         c->opcode, regs.pc, c->final_.pc, got_a, c->final_.a,
338
		         got_x, c->final_.x, got_y, c->final_.y,
339
		         regs.status, c->final_.p, got_s, want_s);
340
	}
341
342
	if (spent == c->cycle_n) {
343
		r->cycles_ok++;
344
		op->cycles_ok++;
345
		by_mode[mode].cycles_ok++;
346
	}
347
348
	bool trace = (bus_n == c->cycle_n);
349
	for (int i = 0; trace && i < bus_n; i++) {
350
		if (bus[i].addr != c->cycles[i].addr ||
351
		    (bus[i].flags & PTF_WRITE) != (c->cycles[i].flags & PTF_WRITE)) {
352
			trace = false;
353
		}
354
	}
355
	if (trace) {
356
		r->bus_ok++;
357
		op->bus_ok++;
358
	}
359
}
360
361
// ctest may run this from the source root or the build directory. Resolve once
362
// rather than giving every file its own candidate list.
363
static const char *
364
tests_prefix(void)
365
{
366
	static const char *prefix = NULL;
367
	if (prefix) {
368
		return prefix;
369
	}
370
	const char *candidates[] = { "", "../", "../../" };
371
	for (size_t i = 0; i < sizeof candidates / sizeof candidates[0]; i++) {
372
		char probe[256];
373
		snprintf(probe, sizeof probe, "%stests/pt_convert.py", candidates[i]);
374
		FILE *f = fopen(probe, "rb");
375
		if (f) {
376
			fclose(f);
377
			prefix = candidates[i];
378
			return prefix;
379
		}
380
	}
381
	prefix = "";
382
	return prefix;
383
}
384
385
static const char *
386
tests_path(char *buf, size_t buflen, const char *rel)
387
{
388
	snprintf(buf, buflen, "%stests/%s", tests_prefix(), rel);
389
	return buf;
390
}
391
392
// Which suite to run. One binary rather than three: the fixtures differ only in
393
// which cases they hold, and the runner already reads the CPU from each case.
394
static const char *variant = "wdc65c02";
395
396
static const char *
397
find_fixture(char *buf, size_t buflen)
398
{
399
	char rel[128];
400
	snprintf(rel, sizeof rel, "pt/%s.bin", variant);
401
	tests_path(buf, buflen, rel);
402
	FILE *f = fopen(buf, "rb");
403
	if (f) {
404
		fclose(f);
405
		return buf;
406
	}
407
	return NULL;
408
}
409
410
// Baseline: cases passing each check, per opcode. Committed; the fixtures are
411
// not. A diff to this file is the record of the core's conformance changing.
412
//
413
// Compared in both directions. A drop is a regression; a rise fails too, since
414
// a baseline is only useful while it stays true. Both are resolved by
415
// regenerating it and reading the diff.
416
//
417
// Fixes belong in src/cpu and touch nothing here, so they can go upstream.
418
static const char *
419
find_baseline(char *buf, size_t buflen)
420
{
421
	char rel[128];
422
	snprintf(rel, sizeof rel, "pt_baseline_%s.txt", variant);
423
	tests_path(buf, buflen, rel);
424
	FILE *f = fopen(buf, "rb");
425
	if (f) {
426
		fclose(f);
427
		return buf;
428
	}
429
	return NULL;
430
}
431
432
static const char *
433
baseline_path(char *buf, size_t buflen)
434
{
435
	char rel[128];
436
	snprintf(rel, sizeof rel, "pt_baseline_%s.txt", variant);
437
	return tests_path(buf, buflen, rel);
438
}
439
440
static void
441
write_baseline(const char *path, long per_opcode)
442
{
443
	FILE *f = fopen(path, "wb");
444
	if (!f) {
445
		printf("     could not write %s\n", path);
446
		return;
447
	}
448
	fprintf(f, "# ProcessorTests results for %s: cases matching hardware,\n", variant);
449
	fprintf(f, "# per opcode. Anything short of the case count is a divergence.\n");
450
	fprintf(f, "# state-b ignores bit 4 of P, which the core forces on.\n");
451
	fprintf(f, "# Regenerate with X16_PT_WRITE_BASELINE=1 and commit the diff.\n");
452
	fprintf(f, "cases_per_opcode %ld\n", per_opcode);
453
	fprintf(f, "# op state state-b cycles bus\n");
454
	for (int i = 0; i < 256; i++) {
455
		pt_opcode *op = &by_opcode[i];
456
		if (op->total > 0) {
457
			fprintf(f, "%02X %ld %ld %ld %ld\n", i, op->state_ok,
458
			        op->state_ok_ignoring_b, op->cycles_ok, op->bus_ok);
459
		}
460
	}
461
	fclose(f);
462
	printf("     wrote %s\n", path);
463
}
464
465
// Returns the number of opcodes disagreeing with the baseline, or -1 if the
466
// baseline was built against a different sample size and cannot be compared.
467
static int
468
compare_baseline(const char *path, long per_opcode)
469
{
470
	FILE *f = fopen(path, "rb");
471
	if (!f) {
472
		return 0;
473
	}
474
	long want[256][4];
475
	bool seen[256];
476
	long baseline_per_opcode = -1;
477
	char line[160];
478
	for (int i = 0; i < 256; i++) {
479
		seen[i] = false;
480
		for (int j = 0; j < 4; j++) {
481
			want[i][j] = 0;
482
		}
483
	}
484
	while (fgets(line, sizeof line, f)) {
485
		if (line[0] == '#' || line[0] == '\n') {
486
			continue;
487
		}
488
		long v;
489
		if (sscanf(line, "cases_per_opcode %ld", &v) == 1) {
490
			baseline_per_opcode = v;
491
			continue;
492
		}
493
		unsigned op;
494
		long a, b, cc, d;
495
		if (sscanf(line, "%x %ld %ld %ld %ld", &op, &a, &b, &cc, &d) == 5 &&
496
		    op < 256) {
497
			want[op][0] = a;
498
			want[op][1] = b;
499
			want[op][2] = cc;
500
			want[op][3] = d;
501
			seen[op] = true;
502
		}
503
	}
504
	fclose(f);
505
506
	if (baseline_per_opcode != per_opcode) {
507
		printf("     baseline covers %ld cases per opcode, this run has %ld\n",
508
		       baseline_per_opcode, per_opcode);
509
		return -1;
510
	}
511
512
	static const char *label[4] = { "state", "state-b", "cycles", "bus" };
513
	int bad = 0;
514
	for (int i = 0; i < 256; i++) {
515
		pt_opcode *op = &by_opcode[i];
516
		if (op->total == 0) {
517
			continue;
518
		}
519
		if (!seen[i]) {
520
			printf("     opcode $%02X: not in the baseline\n", i);
521
			bad++;
522
			continue;
523
		}
524
		long got[4] = { op->state_ok, op->state_ok_ignoring_b, op->cycles_ok,
525
			            op->bus_ok };
526
		for (int j = 0; j < 4; j++) {
527
			if (got[j] != want[i][j]) {
528
				printf("     opcode $%02X %s: %ld/%ld, baseline says %ld (%s)\n",
529
				       i, label[j], got[j], op->total, want[i][j],
530
				       got[j] < want[i][j] ? "REGRESSION"
531
				                           : "improved, record it");
532
				bad++;
533
			}
534
		}
535
	}
536
	return bad;
537
}
538
539
static void
540
write_report(long per_opcode)
541
{
542
	char rpath[256], rel[128];
543
	snprintf(rel, sizeof rel, "../pt-report-%s.txt", variant);
544
	tests_path(rpath, sizeof rpath, rel);
545
	FILE *f = fopen(rpath, "wb");
546
	if (!f) {
547
		return;
548
	}
549
	fprintf(f, "# ProcessorTests per-opcode results for %s, %ld cases per opcode.\n",
550
	        variant, per_opcode);
551
	fprintf(f, "# state-b ignores bit 4 of P, which the core forces on.\n");
552
	fprintf(f, "opcode  state       state-b     cycles      bus         first mismatch (ignoring P bit 4)\n");
553
	for (int i = 0; i < 256; i++) {
554
		pt_opcode *op = &by_opcode[i];
555
		if (op->total == 0) {
556
			continue;
557
		}
558
		fprintf(f, "$%02X     %5ld/%-5ld %5ld/%-5ld %5ld/%-5ld %5ld/%-5ld %s\n",
559
		        i, op->state_ok, op->total, op->state_ok_ignoring_b, op->total,
560
		        op->cycles_ok, op->total, op->bus_ok, op->total,
561
		        op->have_sample ? op->sample : "");
562
	}
563
	fclose(f);
564
	printf("     wrote %s (%ld cases per opcode)\n", rpath, per_opcode);
565
}
566
567
int
568
main(int argc, char **argv)
569
{
570
	if (argc > 1) {
571
		variant = argv[1];
572
	}
573
574
	char label[64];
575
	snprintf(label, sizeof label, "processor_tests[%s]", variant);
576
577
	char path[256];
578
	if (!find_fixture(path, sizeof path)) {
579
		printf("skip: no %s fixture (see tests/fetch_pt.py)\n", variant);
580
		return x16_test_summary(label);
581
	}
582
583
	FILE *f = fopen(path, "rb");
584
	char magic[4];
585
	uint32_t version, count;
586
	if (!read_exact(f, magic, 4) || memcmp(magic, "X16P", 4) != 0 ||
587
	    !read_exact(f, &version, 4) || !read_exact(f, &count, 4)) {
588
		printf("FAIL: %s is not a fixture file\n", path);
589
		fclose(f);
590
		return 1;
591
	}
592
	if (version != 2) {
593
		printf("FAIL: %s is format %u, this reads 2 -- rebuild with "
594
		       "tests/fetch_pt.py --force\n", path, version);
595
		fclose(f);
596
		return 1;
597
	}
598
599
	printf("     %s: %u cases\n", path, count);
600
601
	pt_result r;
602
	memset(&r, 0, sizeof r);
603
	pt_case c;
604
	while (read_case(f, &c)) {
605
		run_case(&c, &r);
606
	}
607
	fclose(f);
608
609
	// Stopping early would otherwise look like a small, clean suite.
610
	if (r.total != (long)count) {
611
		printf("FAIL: read %ld of %u cases (%s)\n", r.total, count,
612
		       read_error ? read_error : "unexpected end of file");
613
		return 1;
614
	}
615
616
	// The baseline holds per-opcode counts, so it only compares against a run
617
	// that converted the same number for each.
618
	long per_opcode = -1;
619
	int  opcodes_seen = 0;
620
	bool even = true;
621
	for (int i = 0; i < 256; i++) {
622
		if (by_opcode[i].total == 0) {
623
			continue;
624
		}
625
		opcodes_seen++;
626
		if (per_opcode < 0) {
627
			per_opcode = by_opcode[i].total;
628
		} else if (by_opcode[i].total != per_opcode) {
629
			even = false;
630
		}
631
	}
632
633
	printf("     %d opcodes, %ld cases each\n", opcodes_seen, per_opcode);
634
	printf("     final state : %ld/%ld\n", r.state_ok, r.total);
635
	printf("     ...ignoring P bit 4 : %ld/%ld\n", r.state_ok_ignoring_b, r.total);
636
	printf("     cycle count : %ld/%ld\n", r.cycles_ok, r.total);
637
	printf("     bus trace   : %ld/%ld\n", r.bus_ok, r.total);
638
	for (int i = 0; i < MODE_COUNT; i++) {
639
		if (by_mode[i].total > 0) {
640
			printf("     %-15s state %ld/%ld  cycles %ld/%ld\n", mode_name[i],
641
			       by_mode[i].state_ok, by_mode[i].total,
642
			       by_mode[i].cycles_ok, by_mode[i].total);
643
		}
644
	}
645
	if (r.have_first_bad) {
646
		printf("     first state mismatch: %s\n", r.first_bad);
647
	}
648
	write_report(per_opcode);
649
650
	// A run matching its baseline is still wrong; say so, or green reads as
651
	// correct.
652
	int diverging = 0;
653
	int not_comparable = 0;
654
	for (int i = 0; i < 256; i++) {
655
		pt_opcode *op = &by_opcode[i];
656
		if (op->total == 0) {
657
			continue;
658
		}
659
		if (op->not_comparable) {
660
			not_comparable++;
661
			continue;
662
		}
663
		if (op->state_ok_ignoring_b < op->total || op->cycles_ok < op->total) {
664
			diverging++;
665
		}
666
	}
667
	if (not_comparable > 0) {
668
		printf("     %d opcodes not comparable (see is_not_comparable)\n",
669
		       not_comparable);
670
	}
671
	if (diverging > 0) {
672
		printf("DIVERGE: %s: %d of %d opcodes do not match hardware on state "
673
		       "or cycles -- see pt-report-%s.txt\n",
674
		       variant, diverging, opcodes_seen, variant);
675
	}
676
677
	check(r.total > 0, "the fixture contained cases");
678
679
	char bpath[256];
680
	if (getenv("X16_PT_WRITE_BASELINE")) {
681
		write_baseline(baseline_path(bpath, sizeof bpath), per_opcode);
682
	} else if (!even) {
683
		printf("     uneven case counts per opcode; skipping the baseline check\n");
684
	} else if (!find_baseline(bpath, sizeof bpath)) {
685
		printf("     no baseline yet; run with X16_PT_WRITE_BASELINE=1\n");
686
	} else {
687
		int bad = compare_baseline(bpath, per_opcode);
688
		check(bad >= 0, "the baseline was built for this many cases per opcode");
689
		check(bad <= 0, "every opcode matches its recorded baseline");
690
	}
691
692
	return x16_test_summary(label);
693
}