From 68f810883dfd49fc3391d7c1cc4b46d9b439290d Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Fri, 2 Sep 2022 16:52:27 -0400
Subject: [PATCH 01/12] Begin process of creating on-disk tests.

---
 .../Mac/Clock SignalTests/68000OldVsNew.mm    | 159 ++++++++++++++++++
 1 file changed, 159 insertions(+)

diff --git a/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm b/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm
index 8ba3bd9ef..507decc3d 100644
--- a/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm	
+++ b/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm	
@@ -238,6 +238,85 @@ template <typename M68000> struct Tester {
 	M68000 processor;
 };
 
+void print_state(FILE *target, const CPU::MC68000Mk2::State &state) {
+	for(int c = 0; c < 8; c++) {
+		fprintf(target, "\"D%d\": %u, ", c, state.registers.data[c]);
+	}
+
+	for(int c = 0; c < 7; c++) {
+		fprintf(target, "\"A%d\": %u, ", c, state.registers.address[c]);
+	}
+
+	fprintf(target, "\"USP\": %u, ", state.registers.user_stack_pointer);
+	fprintf(target, "\"SSP\": %u, ", state.registers.supervisor_stack_pointer);
+	fprintf(target, "\"SR\": %u, ", state.registers.status);
+	fprintf(target, "\"PC\": %u", state.registers.program_counter - 4);
+}
+
+void print_transactions(FILE *target, const std::vector<Transaction> &transactions, HalfCycles end) {
+	auto iterator = transactions.begin();
+	bool is_first = true;
+	do {
+		if(!is_first) fprintf(target, ", ");
+		is_first = false;
+		fprintf(target, "[");
+
+		auto next = iterator + 1;
+
+		// Attempt to pair off transactions to reproduct YACHT notation.
+		bool is_access = true;
+		if(!iterator->address_strobe && !iterator->data_strobes) {
+			fprintf(target, "\"n\", ");
+			is_access = false;
+		} else {
+			assert(!iterator->data_strobes);
+			if(next->read) {
+				fprintf(target, "\"nr\", ");
+			} else {
+				fprintf(target, "\"nw\", ");
+			}
+			++next;
+		}
+		HalfCycles length;
+		if(next == transactions.end()) {
+			length = end - iterator->timestamp;
+		} else {
+			length = next->timestamp - iterator->timestamp;
+		}
+		fprintf(target, "%d, ", length.as<int>() >> 1);
+
+		fprintf(target, "%d, ", iterator->function_code);
+
+		if(is_access) {
+			--next;
+			fprintf(target, "%d, ", iterator->address & 0xff'ffff);
+
+			switch(next->data_strobes) {
+				default: assert(false);
+				case 1: {
+					if(next->address & 1) {
+						fprintf(target, "\"-L\", ");
+					} else {
+						fprintf(target, "\"U-\", ");
+					}
+				} break;
+				case 2: fprintf(target, "\"UL\", "); break;
+				break;
+			}
+			fprintf(target, "%d", next->value);
+
+			++next;
+		} else {
+			fprintf(target, "null, ");
+			fprintf(target, "\"--\", ");
+			fprintf(target, "null");
+		}
+
+		fprintf(target, "]");
+		iterator = next;
+	} while(iterator != transactions.end());
+}
+
 }
 
 @interface M68000OldVsNewTests : XCTestCase
@@ -245,6 +324,86 @@ template <typename M68000> struct Tester {
 
 @implementation M68000OldVsNewTests
 
+- (void)testGenerate {
+	srand(68000);
+
+	NSString *const tempDir = NSTemporaryDirectory();
+	NSLog(@"Outputting to %@", tempDir);
+
+	RandomStore random_store;
+	auto tester = std::make_unique<Tester<NewProcessor>>(random_store, 0x02);
+	InstructionSet::M68k::Predecoder<InstructionSet::M68k::Model::M68000> decoder;
+
+	for(int c = 0; c < 65536; c++) {
+		// Test only defined opcodes that aren't STOP (which will never teminate).
+		const auto instruction = decoder.decode(uint16_t(c));
+		if(
+			instruction.operation == InstructionSet::M68k::Operation::Undefined ||
+			instruction.operation == InstructionSet::M68k::Operation::STOP
+		) {
+			continue;
+		}
+
+		NSString *const targetName = [NSString stringWithFormat:@"%@%04x.json", tempDir, c];
+		FILE *const target = fopen(targetName.UTF8String, "wt");
+
+		bool is_first_test = true;
+		fprintf(target, "[");
+
+		// Test each 10000 times.
+		for(int test = 0; test < 10000; test++) {
+			if(!is_first_test) fprintf(target, ",\n");
+			is_first_test = false;
+
+			// Establish with certainty the initial memory state.
+			random_store.clear();
+			tester->reset_with_opcode(c);
+
+			// Generate a random initial register state.
+			auto initialState = tester->processor.get_state();
+
+			for(int c = 0; c < 8; c++) {
+				initialState.registers.data[c] = rand() ^ (rand() << 1);
+				if(c != 7) initialState.registers.address[c] = rand() << 1;
+			}
+			// Fully to paper over the two 68000s' different ways of doing a faked
+			// reset, pick a random status such that:
+			//
+			//	(i) supervisor mode is active;
+			//	(ii) trace is inactive; and
+			//	(iii) interrupt level is 7.
+			initialState.registers.status = (rand() | (1 << 13) | (7 << 8)) & ~(1 << 15);
+			initialState.registers.user_stack_pointer = rand() << 1;
+			initialState.registers.supervisor_stack_pointer = 0x800;
+
+			// Dump initial state.
+			fprintf(target, "{ \"name\": \"%04x %s %d\", ", c, instruction.to_string().c_str(), test + 1);
+			fprintf(target, "\"initial\": {");
+			print_state(target, initialState);
+
+			tester->processor.set_state(initialState);
+
+			// Run a single instruction.
+			tester->run_instructions(1);
+
+			// Grab and output final states.
+			const auto finalState = tester->processor.get_state();
+			fprintf(target, "}, \"final\": {");
+			print_state(target, finalState);
+
+			// Output total length and bus activity.
+			fprintf(target, "}, \"length\": %d, ", tester->bus_handler.time.as<int>() >> 1);
+
+			fprintf(target, "\"transactions\": [");
+			print_transactions(target, tester->bus_handler.transactions, tester->bus_handler.time);
+			fprintf(target, "]}");
+		}
+
+		fprintf(target, "\n]\n");
+		fclose(target);
+	}
+}
+
 - (void)testOldVsNew {
 	RandomStore random_store;
 	auto oldTester = std::make_unique<Tester<OldProcessor>>(random_store, 0x01);

From cee3f7805962ffdec3c07591c245d8a1dd8c4e5c Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Sat, 3 Sep 2022 15:45:06 -0400
Subject: [PATCH 02/12] Attempt to output only relevant RAM.

---
 .../Mac/Clock SignalTests/68000OldVsNew.mm    | 90 ++++++++++++++++---
 1 file changed, 79 insertions(+), 11 deletions(-)

diff --git a/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm b/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm
index 507decc3d..3598a2410 100644
--- a/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm	
+++ b/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm	
@@ -15,6 +15,7 @@
 #include <array>
 #include <unordered_map>
 #include <set>
+#include <map>
 
 namespace {
 
@@ -116,7 +117,7 @@ struct BusHandler {
 		time += cycle.length;
 
 		// Do the operation...
-		const uint32_t address = cycle.address ? (*cycle.address & 0xffff'ff) : 0;
+		const uint32_t address = cycle.address ? (*cycle.address & 0xff'ffff) : 0;
 		switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) {
 			default: break;
 
@@ -153,7 +154,6 @@ struct BusHandler {
 			break;
 		}
 
-
 		// Add the data value if relevant.
 		if(transaction.data_strobes) {
 			transaction.value = cycle.value16();
@@ -238,7 +238,7 @@ template <typename M68000> struct Tester {
 	M68000 processor;
 };
 
-void print_state(FILE *target, const CPU::MC68000Mk2::State &state) {
+void print_state(FILE *target, const CPU::MC68000Mk2::State &state, const std::vector<Transaction> &transactions, bool is_initial) {
 	for(int c = 0; c < 8; c++) {
 		fprintf(target, "\"D%d\": %u, ", c, state.registers.data[c]);
 	}
@@ -250,7 +250,73 @@ void print_state(FILE *target, const CPU::MC68000Mk2::State &state) {
 	fprintf(target, "\"USP\": %u, ", state.registers.user_stack_pointer);
 	fprintf(target, "\"SSP\": %u, ", state.registers.supervisor_stack_pointer);
 	fprintf(target, "\"SR\": %u, ", state.registers.status);
-	fprintf(target, "\"PC\": %u", state.registers.program_counter - 4);
+	fprintf(target, "\"PC\": %u, ", state.registers.program_counter - 4);
+
+	fprintf(target, "\"ram\": [");
+
+	// Compute RAM from transactions; if this is the initial state then RAM should
+	// be everything that was subject to a read which had not previously been
+	// subject to a write. Otherwise it can just be everything.
+	std::map<uint32_t, uint8_t> ram;
+	if(is_initial) {
+		std::set<uint32_t> written_addresses;
+
+		for(const auto &transaction: transactions) {
+			switch(transaction.data_strobes) {
+				default: continue;
+				case 1:
+					if(transaction.read) {
+						if(ram.find(transaction.address) == ram.end()) {
+							ram[transaction.address] = transaction.value;
+						}
+					} else {
+						written_addresses.insert(transaction.address);
+					}
+				break;
+				case 2:
+					if(transaction.read) {
+						if(ram.find(transaction.address) == ram.end()) {
+							ram[transaction.address] = uint8_t(transaction.value >> 8);
+						}
+						if(ram.find(transaction.address+1) == ram.end()) {
+							ram[transaction.address+1] = uint8_t(transaction.value);
+						}
+					} else {
+						written_addresses.insert(transaction.address);
+						written_addresses.insert(transaction.address + 1);
+					}
+				break;
+			}
+		}
+	} else {
+		for(const auto &transaction: transactions) {
+			switch(transaction.data_strobes) {
+				default: continue;
+				case 1:
+					if(ram.find(transaction.address) == ram.end()) {
+						ram[transaction.address] = transaction.value;
+					}
+				break;
+				case 2:
+					if(ram.find(transaction.address) == ram.end()) {
+						ram[transaction.address] = uint8_t(transaction.value >> 8);
+					}
+					if(ram.find(transaction.address+1) == ram.end()) {
+						ram[transaction.address+1] = uint8_t(transaction.value);
+					}
+				break;
+			}
+		}
+
+	}
+
+	bool is_first = true;
+	for(const auto &pair: ram) {
+		if(!is_first) fprintf(target, ", ");
+		is_first = false;
+		fprintf(target, "[%d, %d]", pair.first, pair.second);
+	}
+	fprintf(target, "]");
 }
 
 void print_transactions(FILE *target, const std::vector<Transaction> &transactions, HalfCycles end) {
@@ -376,20 +442,22 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
 			initialState.registers.user_stack_pointer = rand() << 1;
 			initialState.registers.supervisor_stack_pointer = 0x800;
 
-			// Dump initial state.
-			fprintf(target, "{ \"name\": \"%04x %s %d\", ", c, instruction.to_string().c_str(), test + 1);
-			fprintf(target, "\"initial\": {");
-			print_state(target, initialState);
-
+			// Set state.
 			tester->processor.set_state(initialState);
 
 			// Run a single instruction.
 			tester->run_instructions(1);
 
-			// Grab and output final states.
 			const auto finalState = tester->processor.get_state();
+
+			// Output initial state.
+			fprintf(target, "{ \"name\": \"%04x [%s] %d\", ", c, instruction.to_string().c_str(), test + 1);
+			fprintf(target, "\"initial\": {");
+			print_state(target, initialState, tester->bus_handler.transactions, true);
+
+			// Output final state.
 			fprintf(target, "}, \"final\": {");
-			print_state(target, finalState);
+			print_state(target, finalState, tester->bus_handler.transactions, false);
 
 			// Output total length and bus activity.
 			fprintf(target, "}, \"length\": %d, ", tester->bus_handler.time.as<int>() >> 1);

From effe8c102d862dff3a6a907abd78d4a70e671caa Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Mon, 5 Sep 2022 21:52:20 -0400
Subject: [PATCH 03/12] Provide a direct `to_string` on `Operation`.

---
 InstructionSets/M68k/Instruction.cpp | 324 +++++++++++++--------------
 InstructionSets/M68k/Instruction.hpp |   2 +
 2 files changed, 152 insertions(+), 174 deletions(-)

diff --git a/InstructionSets/M68k/Instruction.cpp b/InstructionSets/M68k/Instruction.cpp
index c52ebe0ee..16420bb8d 100644
--- a/InstructionSets/M68k/Instruction.cpp
+++ b/InstructionSets/M68k/Instruction.cpp
@@ -56,233 +56,209 @@ std::string Preinstruction::operand_description(int index, int opcode) const {
 	}
 }
 
-std::string Preinstruction::to_string(int opcode) const {
-	bool flip_operands = false;
-	const char *instruction;
+namespace {
 
+const char *_to_string(Operation operation, bool is_quick) {
 	switch(operation) {
-		case Operation::Undefined:	return "None";
-		case Operation::NOP:		instruction = "NOP";		break;
-		case Operation::ABCD:		instruction = "ABCD";		break;
-		case Operation::SBCD:		instruction = "SBCD";		break;
-		case Operation::NBCD:		instruction = "NBCD";		break;
+		case Operation::Undefined:		return "None";
+		case Operation::NOP:			return "NOP";
+		case Operation::ABCD:			return "ABCD";
+		case Operation::SBCD:			return "SBCD";
+		case Operation::NBCD:			return "NBCD";
 
-		case Operation::ADDb:		instruction = "ADD.b";		break;
-		case Operation::ADDw:		instruction = "ADD.w";		break;
-		case Operation::ADDl:		instruction = "ADD.l";		break;
+		case Operation::ADDb:			return "ADD.b";
+		case Operation::ADDw:			return "ADD.w";
+		case Operation::ADDl:			return "ADD.l";
 
-		case Operation::ADDAw:
-			if(mode<0>() == AddressingMode::Quick) {
-				instruction = "ADD.w";
-			} else {
-				instruction = "ADDA.w";
-			}
-		break;
-		case Operation::ADDAl:
-			if(mode<0>() == AddressingMode::Quick) {
-				instruction = "ADD.l";
-			} else {
-				instruction = "ADDA.l";
-			}
-		break;
+		case Operation::ADDAw:			return is_quick ? "ADD.w" : "ADDA.w";
+		case Operation::ADDAl:			return is_quick ? "ADD.l" : "ADDA.l";
 
-		case Operation::ADDXb:		instruction = "ADDX.b";	break;
-		case Operation::ADDXw:		instruction = "ADDX.w";	break;
-		case Operation::ADDXl:		instruction = "ADDX.l";	break;
+		case Operation::ADDXb:			return "ADDX.b";
+		case Operation::ADDXw:			return "ADDX.w";
+		case Operation::ADDXl:			return "ADDX.l";
 
-		case Operation::SUBb:		instruction = "SUB.b";	break;
-		case Operation::SUBw:		instruction = "SUB.w";	break;
-		case Operation::SUBl:		instruction = "SUB.l";	break;
+		case Operation::SUBb:			return "SUB.b";
+		case Operation::SUBw:			return "SUB.w";
+		case Operation::SUBl:			return "SUB.l";
 
-		case Operation::SUBAw:
-			if(mode<0>() == AddressingMode::Quick) {
-				instruction = "SUB.w";
-			} else {
-				instruction = "SUBA.w";
-			}
-		break;
-		case Operation::SUBAl:
-			if(mode<0>() == AddressingMode::Quick) {
-				instruction = "SUB.l";
-			} else {
-				instruction = "SUBA.l";
-			}
-		break;
+		case Operation::SUBAw:			return is_quick ? "SUB.w" : "SUBA.w";
+		case Operation::SUBAl:			return is_quick ? "SUB.l" : "SUBA.l";
 
-		case Operation::SUBXb:		instruction = "SUBX.b";	break;
-		case Operation::SUBXw:		instruction = "SUBX.w";	break;
-		case Operation::SUBXl:		instruction = "SUBX.l";	break;
+		case Operation::SUBXb:			return "SUBX.b";
+		case Operation::SUBXw:			return "SUBX.w";
+		case Operation::SUBXl:			return "SUBX.l";
 
-		case Operation::MOVEb:		instruction = "MOVE.b";	break;
-		case Operation::MOVEw:		instruction = "MOVE.w";	break;
-		case Operation::MOVEl:
-			if(mode<0>() == AddressingMode::Quick) {
-				instruction = "MOVE.q";
-			} else {
-				instruction = "MOVE.l";
-			}
-		break;
+		case Operation::MOVEb:			return "MOVE.b";
+		case Operation::MOVEw:			return "MOVE.w";
+		case Operation::MOVEl:			return is_quick ? "MOVE.q" : "MOVE.l";
 
-		case Operation::MOVEAw:		instruction = "MOVEA.w";	break;
-		case Operation::MOVEAl:		instruction = "MOVEA.l";	break;
+		case Operation::MOVEAw:			return "MOVEA.w";
+		case Operation::MOVEAl:			return "MOVEA.l";
 
-		case Operation::LEA:		instruction = "LEA";		break;
-		case Operation::PEA:		instruction = "PEA";		break;
+		case Operation::LEA:			return "LEA";
+		case Operation::PEA:			return "PEA";
 
-		case Operation::MOVEtoSR:		instruction = "MOVEtoSR";		break;
-		case Operation::MOVEfromSR:		instruction = "MOVEfromSR";		break;
-		case Operation::MOVEtoCCR:		instruction = "MOVEtoCCR";		break;
-		case Operation::MOVEtoUSP:		instruction = "MOVEtoUSP";		break;
-		case Operation::MOVEfromUSP:	instruction = "MOVEfromUSP";	break;
+		case Operation::MOVEtoSR:		return "MOVEtoSR";
+		case Operation::MOVEfromSR:		return "MOVEfromSR";
+		case Operation::MOVEtoCCR:		return "MOVEtoCCR";
+		case Operation::MOVEtoUSP:		return "MOVEtoUSP";
+		case Operation::MOVEfromUSP:	return "MOVEfromUSP";
 
-		case Operation::ORItoSR:	instruction = "ORItoSR";	break;
-		case Operation::ORItoCCR:	instruction = "ORItoCCR";	break;
-		case Operation::ANDItoSR:	instruction = "ANDItoSR";	break;
-		case Operation::ANDItoCCR:	instruction = "ANDItoCCR";	break;
-		case Operation::EORItoSR:	instruction = "EORItoSR";	break;
-		case Operation::EORItoCCR:	instruction = "EORItoCCR";	break;
+		case Operation::ORItoSR:		return "ORItoSR";
+		case Operation::ORItoCCR:		return "ORItoCCR";
+		case Operation::ANDItoSR:		return "ANDItoSR";
+		case Operation::ANDItoCCR:		return "ANDItoCCR";
+		case Operation::EORItoSR:		return "EORItoSR";
+		case Operation::EORItoCCR:		return "EORItoCCR";
 
-		case Operation::BTST:	instruction = "BTST";	break;
-		case Operation::BCLR:	instruction = "BCLR";	break;
-		case Operation::BCHG:	instruction = "BCHG";	break;
-		case Operation::BSET:	instruction = "BSET";	break;
+		case Operation::BTST:			return "BTST";
+		case Operation::BCLR:			return "BCLR";
+		case Operation::BCHG:			return "BCHG";
+		case Operation::BSET:			return "BSET";
 
-		case Operation::CMPb:	instruction = "CMP.b";	break;
-		case Operation::CMPw:	instruction = "CMP.w";	break;
-		case Operation::CMPl:	instruction = "CMP.l";	break;
+		case Operation::CMPb:			return "CMP.b";
+		case Operation::CMPw:			return "CMP.w";
+		case Operation::CMPl:			return "CMP.l";
 
-		case Operation::CMPAw:	instruction = "CMPA.w";	break;
-		case Operation::CMPAl:	instruction = "CMPA.l";	break;
+		case Operation::CMPAw:			return "CMPA.w";
+		case Operation::CMPAl:			return "CMPA.l";
 
-		case Operation::TSTb:	instruction = "TST.b";	break;
-		case Operation::TSTw:	instruction = "TST.w";	break;
-		case Operation::TSTl:	instruction = "TST.l";	break;
+		case Operation::TSTb:			return "TST.b";
+		case Operation::TSTw:			return "TST.w";
+		case Operation::TSTl:			return "TST.l";
 
-		case Operation::JMP:	instruction = "JMP";	break;
-		case Operation::JSR:	instruction = "JSR";	break;
-		case Operation::RTS:	instruction = "RTS";	break;
-		case Operation::DBcc:	instruction = "DBcc";	break;
-		case Operation::Scc:	instruction = "Scc";	break;
+		case Operation::JMP:			return "JMP";
+		case Operation::JSR:			return "JSR";
+		case Operation::RTS:			return "RTS";
+		case Operation::DBcc:			return "DBcc";
+		case Operation::Scc:			return "Scc";
 
 		case Operation::Bccb:
 		case Operation::Bccl:
-		case Operation::Bccw:	instruction = "Bcc";	break;
+		case Operation::Bccw:			return "Bcc";
 
 		case Operation::BSRb:
 		case Operation::BSRl:
-		case Operation::BSRw:	instruction = "BSR";	break;
+		case Operation::BSRw:			return "BSR";
 
-		case Operation::CLRb:	instruction = "CLR.b";	break;
-		case Operation::CLRw:	instruction = "CLR.w";	break;
-		case Operation::CLRl:	instruction = "CLR.l";	break;
+		case Operation::CLRb:			return "CLR.b";
+		case Operation::CLRw:			return "CLR.w";
+		case Operation::CLRl:			return "CLR.l";
 
-		case Operation::NEGXb:	instruction = "NEGX.b";	break;
-		case Operation::NEGXw:	instruction = "NEGX.w";	break;
-		case Operation::NEGXl:	instruction = "NEGX.l";	break;
+		case Operation::NEGXb:			return "NEGX.b";
+		case Operation::NEGXw:			return "NEGX.w";
+		case Operation::NEGXl:			return "NEGX.l";
 
-		case Operation::NEGb:	instruction = "NEG.b";	break;
-		case Operation::NEGw:	instruction = "NEG.w";	break;
-		case Operation::NEGl:	instruction = "NEG.l";	break;
+		case Operation::NEGb:			return "NEG.b";
+		case Operation::NEGw:			return "NEG.w";
+		case Operation::NEGl:			return "NEG.l";
 
-		case Operation::ASLb:	instruction = "ASL.b";	break;
-		case Operation::ASLw:	instruction = "ASL.w";	break;
-		case Operation::ASLl:	instruction = "ASL.l";	break;
-		case Operation::ASLm:	instruction = "ASL.w";	break;
+		case Operation::ASLb:			return "ASL.b";
+		case Operation::ASLw:			return "ASL.w";
+		case Operation::ASLl:			return "ASL.l";
+		case Operation::ASLm:			return "ASL.w";
 
-		case Operation::ASRb:	instruction = "ASR.b";	break;
-		case Operation::ASRw:	instruction = "ASR.w";	break;
-		case Operation::ASRl:	instruction = "ASR.l";	break;
-		case Operation::ASRm:	instruction = "ASR.w";	break;
+		case Operation::ASRb:			return "ASR.b";
+		case Operation::ASRw:			return "ASR.w";
+		case Operation::ASRl:			return "ASR.l";
+		case Operation::ASRm:			return "ASR.w";
 
-		case Operation::LSLb:	instruction = "LSL.b";	break;
-		case Operation::LSLw:	instruction = "LSL.w";	break;
-		case Operation::LSLl:	instruction = "LSL.l";	break;
-		case Operation::LSLm:	instruction = "LSL.w";	break;
+		case Operation::LSLb:			return "LSL.b";
+		case Operation::LSLw:			return "LSL.w";
+		case Operation::LSLl:			return "LSL.l";
+		case Operation::LSLm:			return "LSL.w";
 
-		case Operation::LSRb:	instruction = "LSR.b";	break;
-		case Operation::LSRw:	instruction = "LSR.w";	break;
-		case Operation::LSRl:	instruction = "LSR.l";	break;
-		case Operation::LSRm:	instruction = "LSR.w";	break;
+		case Operation::LSRb:			return "LSR.b";
+		case Operation::LSRw:			return "LSR.w";
+		case Operation::LSRl:			return "LSR.l";
+		case Operation::LSRm:			return "LSR.w";
 
-		case Operation::ROLb:	instruction = "ROL.b";	break;
-		case Operation::ROLw:	instruction = "ROL.w";	break;
-		case Operation::ROLl:	instruction = "ROL.l";	break;
-		case Operation::ROLm:	instruction = "ROL.w";	break;
+		case Operation::ROLb:			return "ROL.b";
+		case Operation::ROLw:			return "ROL.w";
+		case Operation::ROLl:			return "ROL.l";
+		case Operation::ROLm:			return "ROL.w";
 
-		case Operation::RORb:	instruction = "ROR.b";	break;
-		case Operation::RORw:	instruction = "ROR.w";	break;
-		case Operation::RORl:	instruction = "ROR.l";	break;
-		case Operation::RORm:	instruction = "ROR.w";	break;
+		case Operation::RORb:			return "ROR.b";
+		case Operation::RORw:			return "ROR.w";
+		case Operation::RORl:			return "ROR.l";
+		case Operation::RORm:			return "ROR.w";
 
-		case Operation::ROXLb:	instruction = "ROXL.b";	break;
-		case Operation::ROXLw:	instruction = "ROXL.w";	break;
-		case Operation::ROXLl:	instruction = "ROXL.l";	break;
-		case Operation::ROXLm:	instruction = "ROXL.w";	break;
+		case Operation::ROXLb:			return "ROXL.b";
+		case Operation::ROXLw:			return "ROXL.w";
+		case Operation::ROXLl:			return "ROXL.l";
+		case Operation::ROXLm:			return "ROXL.w";
 
-		case Operation::ROXRb:	instruction = "ROXR.b";	break;
-		case Operation::ROXRw:	instruction = "ROXR.w";	break;
-		case Operation::ROXRl:	instruction = "ROXR.l";	break;
-		case Operation::ROXRm:	instruction = "ROXR.w";	break;
+		case Operation::ROXRb:			return "ROXR.b";
+		case Operation::ROXRw:			return "ROXR.w";
+		case Operation::ROXRl:			return "ROXR.l";
+		case Operation::ROXRm:			return "ROXR.w";
 
-		case Operation::MOVEMtoMl:	instruction = "MOVEM.l";	break;
-		case Operation::MOVEMtoMw:	instruction = "MOVEM.w";	break;
-		case Operation::MOVEMtoRl:
-			instruction = "MOVEM.l";
-			flip_operands = true;
-		break;
-		case Operation::MOVEMtoRw:
-			instruction = "MOVEM.w";
-			flip_operands = true;
-		break;
+		case Operation::MOVEMtoMl:		return "MOVEM.l";
+		case Operation::MOVEMtoMw:		return "MOVEM.w";
+		case Operation::MOVEMtoRl:		return "MOVEM.l";
+		case Operation::MOVEMtoRw:		return "MOVEM.w";
 
-		case Operation::MOVEPl:	instruction = "MOVEP.l";	break;
-		case Operation::MOVEPw:	instruction = "MOVEP.w";	break;
+		case Operation::MOVEPl:			return "MOVEP.l";
+		case Operation::MOVEPw:			return "MOVEP.w";
 
-		case Operation::ANDb:	instruction = "AND.b";	break;
-		case Operation::ANDw:	instruction = "AND.w";	break;
-		case Operation::ANDl:	instruction = "AND.l";	break;
+		case Operation::ANDb:			return "AND.b";
+		case Operation::ANDw:			return "AND.w";
+		case Operation::ANDl:			return "AND.l";
 
-		case Operation::EORb:	instruction = "EOR.b";	break;
-		case Operation::EORw:	instruction = "EOR.w";	break;
-		case Operation::EORl:	instruction = "EOR.l";	break;
+		case Operation::EORb:			return "EOR.b";
+		case Operation::EORw:			return "EOR.w";
+		case Operation::EORl:			return "EOR.l";
 
-		case Operation::NOTb:	instruction = "NOT.b";	break;
-		case Operation::NOTw:	instruction = "NOT.w";	break;
-		case Operation::NOTl:	instruction = "NOT.l";	break;
+		case Operation::NOTb:			return "NOT.b";
+		case Operation::NOTw:			return "NOT.w";
+		case Operation::NOTl:			return "NOT.l";
 
-		case Operation::ORb:	instruction = "OR.b";	break;
-		case Operation::ORw:	instruction = "OR.w";	break;
-		case Operation::ORl:	instruction = "OR.l";	break;
+		case Operation::ORb:			return "OR.b";
+		case Operation::ORw:			return "OR.w";
+		case Operation::ORl:			return "OR.l";
 
-		case Operation::MULU:	instruction = "MULU";	break;
-		case Operation::MULS:	instruction = "MULS";	break;
-		case Operation::DIVU:	instruction = "DIVU";	break;
-		case Operation::DIVS:	instruction = "DIVS";	break;
+		case Operation::MULU:			return "MULU";
+		case Operation::MULS:			return "MULS";
+		case Operation::DIVU:			return "DIVU";
+		case Operation::DIVS:			return "DIVS";
 
-		case Operation::RTE:	instruction = "RTE";	break;
-		case Operation::RTR:	instruction = "RTR";	break;
+		case Operation::RTE:			return "RTE";
+		case Operation::RTR:			return "RTR";
 
-		case Operation::TRAP:	instruction = "TRAP";	break;
-		case Operation::TRAPV:	instruction = "TRAPV";	break;
-		case Operation::CHK:	instruction = "CHK";	break;
+		case Operation::TRAP:			return "TRAP";
+		case Operation::TRAPV:			return "TRAPV";
+		case Operation::CHK:			return "CHK";
 
-		case Operation::EXG:	instruction = "EXG";	break;
-		case Operation::SWAP:	instruction = "SWAP";	break;
+		case Operation::EXG:			return "EXG";
+		case Operation::SWAP:			return "SWAP";
 
-		case Operation::TAS:	instruction = "TAS";	break;
+		case Operation::TAS:			return "TAS";
 
-		case Operation::EXTbtow:	instruction = "EXT.w";	break;
-		case Operation::EXTwtol:	instruction = "EXT.l";	break;
+		case Operation::EXTbtow:		return "EXT.w";
+		case Operation::EXTwtol:		return "EXT.l";
 
-		case Operation::LINKw:	instruction = "LINK";	break;
-		case Operation::UNLINK:	instruction = "UNLINK";	break;
+		case Operation::LINKw:			return "LINK";
+		case Operation::UNLINK:			return "UNLINK";
 
-		case Operation::STOP:	instruction = "STOP";	break;
-		case Operation::RESET:	instruction = "RESET";	break;
+		case Operation::STOP:			return "STOP";
+		case Operation::RESET:			return "RESET";
 
 		default:
 			assert(false);
 	}
+}
+
+}
+
+const char *InstructionSet::M68k::to_string(Operation operation) {
+	return _to_string(operation, false);
+}
+
+std::string Preinstruction::to_string(int opcode) const {
+	if(operation == Operation::Undefined) return "None";
+
+	const char *const instruction = _to_string(operation, mode<0>() == AddressingMode::Quick);
+	const bool flip_operands = (operation == Operation::MOVEMtoRl) || (operation == Operation::MOVEMtoRw);
 
 	const std::string operand1 = operand_description(0 ^ int(flip_operands), opcode);
 	const std::string operand2 = operand_description(1 ^ int(flip_operands), opcode);
diff --git a/InstructionSets/M68k/Instruction.hpp b/InstructionSets/M68k/Instruction.hpp
index 75c55ed29..f84c98666 100644
--- a/InstructionSets/M68k/Instruction.hpp
+++ b/InstructionSets/M68k/Instruction.hpp
@@ -104,6 +104,8 @@ enum class Operation: uint8_t {
 	Max = RESET
 };
 
+const char *to_string(Operation op);
+
 template <Model model>
 constexpr bool requires_supervisor(Operation op) {
 	switch(op) {

From b6da1019bd5f7365206f44c88e92dfd346b8a7fe Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Mon, 5 Sep 2022 21:52:48 -0400
Subject: [PATCH 04/12] Bucket tests by operation, aim for ~1,000,000 total.

---
 .../Mac/Clock SignalTests/68000OldVsNew.mm    | 41 +++++++++++++------
 1 file changed, 28 insertions(+), 13 deletions(-)

diff --git a/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm b/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm
index 3598a2410..5bfd534dd 100644
--- a/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm	
+++ b/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm	
@@ -14,8 +14,8 @@
 
 #include <array>
 #include <unordered_map>
+#include <unordered_set>
 #include <set>
-#include <map>
 
 namespace {
 
@@ -257,9 +257,9 @@ void print_state(FILE *target, const CPU::MC68000Mk2::State &state, const std::v
 	// Compute RAM from transactions; if this is the initial state then RAM should
 	// be everything that was subject to a read which had not previously been
 	// subject to a write. Otherwise it can just be everything.
-	std::map<uint32_t, uint8_t> ram;
+	std::unordered_map<uint32_t, uint8_t> ram;
 	if(is_initial) {
-		std::set<uint32_t> written_addresses;
+		std::unordered_set<uint32_t> written_addresses;
 
 		for(const auto &transaction: transactions) {
 			switch(transaction.data_strobes) {
@@ -392,14 +392,12 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
 
 - (void)testGenerate {
 	srand(68000);
-
-	NSString *const tempDir = NSTemporaryDirectory();
-	NSLog(@"Outputting to %@", tempDir);
-
+	InstructionSet::M68k::Predecoder<InstructionSet::M68k::Model::M68000> decoder;
 	RandomStore random_store;
 	auto tester = std::make_unique<Tester<NewProcessor>>(random_store, 0x02);
-	InstructionSet::M68k::Predecoder<InstructionSet::M68k::Model::M68000> decoder;
 
+	// Bucket opcodes by operation.
+	std::unordered_map<const char *, std::vector<uint16_t>> opcodesByOperation;
 	for(int c = 0; c < 65536; c++) {
 		// Test only defined opcodes that aren't STOP (which will never teminate).
 		const auto instruction = decoder.decode(uint16_t(c));
@@ -410,20 +408,37 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
 			continue;
 		}
 
-		NSString *const targetName = [NSString stringWithFormat:@"%@%04x.json", tempDir, c];
+		const auto operation = to_string(instruction.operation);
+		opcodesByOperation[operation].push_back(c);
+	}
+	NSLog(@"Generating for %lu operations", opcodesByOperation.size());
+
+	// Find somewhere to write to.
+	NSString *const tempDir = NSTemporaryDirectory();
+	NSLog(@"Outputting to %@", tempDir);
+
+	// Aim to get  at least 1,000,000 tests total.
+	const auto testsPerOperation = int((1'000'000 + (opcodesByOperation.size() - 1)) / opcodesByOperation.size());
+
+	// Generate by operation.
+	for(const auto &pair: opcodesByOperation) {
+		NSString *const targetName = [NSString stringWithFormat:@"%@%s.json", tempDir, pair.first];
 		FILE *const target = fopen(targetName.UTF8String, "wt");
 
 		bool is_first_test = true;
 		fprintf(target, "[");
 
-		// Test each 10000 times.
-		for(int test = 0; test < 10000; test++) {
+		// Test each for the selected number of iterations.
+		for(int test = 0; test < testsPerOperation; test++) {
 			if(!is_first_test) fprintf(target, ",\n");
 			is_first_test = false;
 
 			// Establish with certainty the initial memory state.
 			random_store.clear();
-			tester->reset_with_opcode(c);
+
+			const auto opcodeIndex = int(rand() * pair.second.size() / RAND_MAX);
+			const uint16_t opcode = pair.second[opcodeIndex];
+			tester->reset_with_opcode(opcode);
 
 			// Generate a random initial register state.
 			auto initialState = tester->processor.get_state();
@@ -451,7 +466,7 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
 			const auto finalState = tester->processor.get_state();
 
 			// Output initial state.
-			fprintf(target, "{ \"name\": \"%04x [%s] %d\", ", c, instruction.to_string().c_str(), test + 1);
+			fprintf(target, "{ \"name\": \"%04x [%s] %d\", ", opcode, decoder.decode(opcode).to_string().c_str(), test + 1);
 			fprintf(target, "\"initial\": {");
 			print_state(target, initialState, tester->bus_handler.transactions, true);
 

From 93c1f7fc905b420061674ac222b7d23974a61927 Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Mon, 5 Sep 2022 22:00:04 -0400
Subject: [PATCH 05/12] Include prefetch in 68000 state.

---
 OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm          | 2 ++
 Processors/68000Mk2/68000Mk2.hpp                           | 1 +
 .../68000Mk2/Implementation/68000Mk2Implementation.hpp     | 7 +++++++
 3 files changed, 10 insertions(+)

diff --git a/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm b/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm
index 5bfd534dd..2d8fc46d0 100644
--- a/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm	
+++ b/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm	
@@ -252,6 +252,8 @@ void print_state(FILE *target, const CPU::MC68000Mk2::State &state, const std::v
 	fprintf(target, "\"SR\": %u, ", state.registers.status);
 	fprintf(target, "\"PC\": %u, ", state.registers.program_counter - 4);
 
+	fprintf(target, "\"prefetch\": [%u, %u], ", state.prefetch[0], state.prefetch[1]);
+
 	fprintf(target, "\"ram\": [");
 
 	// Compute RAM from transactions; if this is the initial state then RAM should
diff --git a/Processors/68000Mk2/68000Mk2.hpp b/Processors/68000Mk2/68000Mk2.hpp
index 00269f06d..be908db02 100644
--- a/Processors/68000Mk2/68000Mk2.hpp
+++ b/Processors/68000Mk2/68000Mk2.hpp
@@ -354,6 +354,7 @@ class BusHandler {
 };
 
 struct State {
+	uint16_t prefetch[2];
 	InstructionSet::M68k::RegisterSet registers;
 };
 
diff --git a/Processors/68000Mk2/Implementation/68000Mk2Implementation.hpp b/Processors/68000Mk2/Implementation/68000Mk2Implementation.hpp
index f6d15c328..f03fe23fe 100644
--- a/Processors/68000Mk2/Implementation/68000Mk2Implementation.hpp
+++ b/Processors/68000Mk2/Implementation/68000Mk2Implementation.hpp
@@ -3025,6 +3025,9 @@ CPU::MC68000Mk2::State Processor<BusHandler, dtack_is_implicit, permit_overrun,
 	state.registers.user_stack_pointer = stack_pointers_[0].l;
 	state.registers.supervisor_stack_pointer = stack_pointers_[1].l;
 
+	state.prefetch[0] = prefetch_.high.w;
+	state.prefetch[1] = prefetch_.low.w;
+
 	return state;
 }
 
@@ -3048,6 +3051,10 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
 
 	// Ensure the local is-supervisor flag is updated.
 	did_update_status();
+
+	// Populate the prefetch.
+	prefetch_.high.w = state.prefetch[0];
+	prefetch_.low.w = state.prefetch[1];
 }
 
 template <class BusHandler, bool dtack_is_implicit, bool permit_overrun, bool signal_will_perform>

From 0fe94b2e6df3102ad59a23ca0797b6680725fceb Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Mon, 5 Sep 2022 22:26:30 -0400
Subject: [PATCH 06/12] Capture ::SameAddress versus ::NewAddress, for TAS
 recognition.

---
 .../Mac/Clock SignalTests/68000OldVsNew.mm    | 49 ++++++++++---------
 1 file changed, 26 insertions(+), 23 deletions(-)

diff --git a/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm b/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm
index 2d8fc46d0..6938d7d25 100644
--- a/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm	
+++ b/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm	
@@ -57,16 +57,18 @@ struct Transaction {
 	uint32_t address = 0;
 	uint16_t value = 0;
 	bool address_strobe = false;
+	bool same_address = false;
 	bool read = false;
 	int data_strobes = 0;
 
-	bool operator !=(const Transaction &rhs) const {
+	bool operator != (const Transaction &rhs) const {
 		if(timestamp != rhs.timestamp) return true;
 //		if(function_code != rhs.function_code) return true;
 		if(address != rhs.address) return true;
 		if(value != rhs.value) return true;
 		if(address_strobe != rhs.address_strobe) return true;
 		if(data_strobes != rhs.data_strobes) return true;
+		if(same_address != rhs.same_address) return true;
 		return false;
 	}
 
@@ -109,6 +111,7 @@ struct BusHandler {
 			transaction.function_code |= (cycle.operation & Microcycle::IsData) ? 0x1 : 0x2;
 		}
 		transaction.address_strobe = cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress);
+		transaction.same_address = cycle.operation & Microcycle::SameAddress;
 		transaction.data_strobes = cycle.operation & (Microcycle::SelectByte | Microcycle::SelectWord);
 		if(cycle.address) transaction.address = *cycle.address & 0xffff'ff;
 		transaction.timestamp = time;
@@ -338,12 +341,23 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
 			is_access = false;
 		} else {
 			assert(!iterator->data_strobes);
-			if(next->read) {
-				fprintf(target, "\"nr\", ");
-			} else {
-				fprintf(target, "\"nw\", ");
+
+			// Check how many transactions this address persists for;
+			// that'll allow a TAS to be recognised here.
+			while(next->same_address && next != transactions.end()) {
+				++next;
+			}
+			--next;
+
+			if(next == iterator + 1) {
+				if(next->read) {
+					fprintf(target, "\"r\", ");
+				} else {
+					fprintf(target, "\"w\", ");
+				}
+			} else {
+				fprintf(target, "\"t\", ");
 			}
-			++next;
 		}
 		HalfCycles length;
 		if(next == transactions.end()) {
@@ -351,33 +365,21 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
 		} else {
 			length = next->timestamp - iterator->timestamp;
 		}
-		fprintf(target, "%d, ", length.as<int>() >> 1);
-
-		fprintf(target, "%d, ", iterator->function_code);
+		fprintf(target, "%d", length.as<int>() >> 1);
 
 		if(is_access) {
-			--next;
+			fprintf(target, ", %d, ", iterator->function_code);
 			fprintf(target, "%d, ", iterator->address & 0xff'ffff);
 
 			switch(next->data_strobes) {
 				default: assert(false);
-				case 1: {
-					if(next->address & 1) {
-						fprintf(target, "\"-L\", ");
-					} else {
-						fprintf(target, "\"U-\", ");
-					}
-				} break;
-				case 2: fprintf(target, "\"UL\", "); break;
+				case 1:	fprintf(target, "\".b\", ");	break;
+				case 2: fprintf(target, "\".w\", "); 	break;
 				break;
 			}
 			fprintf(target, "%d", next->value);
 
 			++next;
-		} else {
-			fprintf(target, "null, ");
-			fprintf(target, "\"--\", ");
-			fprintf(target, "null");
 		}
 
 		fprintf(target, "]");
@@ -413,7 +415,6 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
 		const auto operation = to_string(instruction.operation);
 		opcodesByOperation[operation].push_back(c);
 	}
-	NSLog(@"Generating for %lu operations", opcodesByOperation.size());
 
 	// Find somewhere to write to.
 	NSString *const tempDir = NSTemporaryDirectory();
@@ -423,7 +424,9 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
 	const auto testsPerOperation = int((1'000'000 + (opcodesByOperation.size() - 1)) / opcodesByOperation.size());
 
 	// Generate by operation.
+	NSLog(@"Generating %d tests each for %lu operations", testsPerOperation, opcodesByOperation.size());
 	for(const auto &pair: opcodesByOperation) {
+		NSLog(@"Generating %s", pair.first);
 		NSString *const targetName = [NSString stringWithFormat:@"%@%s.json", tempDir, pair.first];
 		FILE *const target = fopen(targetName.UTF8String, "wt");
 

From 1a7509e86016479192ed9dfb5a061de6fac58bd3 Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Mon, 5 Sep 2022 22:26:45 -0400
Subject: [PATCH 07/12] Properly announce ::SameAddress.

---
 Processors/68000Mk2/Implementation/68000Mk2Implementation.hpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Processors/68000Mk2/Implementation/68000Mk2Implementation.hpp b/Processors/68000Mk2/Implementation/68000Mk2Implementation.hpp
index f03fe23fe..7c1585b62 100644
--- a/Processors/68000Mk2/Implementation/68000Mk2Implementation.hpp
+++ b/Processors/68000Mk2/Implementation/68000Mk2Implementation.hpp
@@ -341,7 +341,7 @@ void Processor<BusHandler, dtack_is_implicit, permit_overrun, signal_will_perfor
 	// Sets up the next data access size and read flags.
 #define SetupDataAccess(read_flag, select_flag)												\
 	access_announce.operation = Microcycle::NewAddress | Microcycle::IsData | (read_flag);	\
-	access.operation = access_announce.operation | (select_flag);
+	access.operation = Microcycle::SameAddress | Microcycle::IsData | (read_flag) | (select_flag);
 
 	// Sets the address source for the next data access.
 #define SetDataAddress(addr)							\

From 72b6ab4389cb39d4437b962f8d4aec2c76fbca6d Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Tue, 6 Sep 2022 11:26:16 -0400
Subject: [PATCH 08/12] Provide a route to operation that factors in addressing
 mode.

---
 InstructionSets/M68k/Instruction.cpp | 4 ++++
 InstructionSets/M68k/Instruction.hpp | 6 ++++++
 2 files changed, 10 insertions(+)

diff --git a/InstructionSets/M68k/Instruction.cpp b/InstructionSets/M68k/Instruction.cpp
index 16420bb8d..e41d0b118 100644
--- a/InstructionSets/M68k/Instruction.cpp
+++ b/InstructionSets/M68k/Instruction.cpp
@@ -269,3 +269,7 @@ std::string Preinstruction::to_string(int opcode) const {
 
 	return result;
 }
+
+const char *Preinstruction::operation_string() const {
+	return _to_string(operation, mode<0>() == AddressingMode::Quick);
+}
diff --git a/InstructionSets/M68k/Instruction.hpp b/InstructionSets/M68k/Instruction.hpp
index f84c98666..de3871762 100644
--- a/InstructionSets/M68k/Instruction.hpp
+++ b/InstructionSets/M68k/Instruction.hpp
@@ -348,6 +348,12 @@ class Preinstruction {
 		/// is supplied then any quick fields in this instruction will be decoded;
 		/// otherwise they'll be printed as just 'Q'.
 		std::string to_string(int opcode = -1) const;
+
+		/// Produces a slightly-more-idiomatic version of the operation name than
+		/// a direct to_string(instruction.operation) would, given that this decoder
+		/// sometimes aliases operations, disambiguating based on addressing mode
+		/// (e.g. MOVEQ is MOVE.l with the Q addressing mode).
+		const char *operation_string() const;
 };
 
 }

From 2c44ddfa95aaf06bfe09d971f8ea1cf43b084050 Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Tue, 6 Sep 2022 11:26:38 -0400
Subject: [PATCH 09/12] Better bucket, and attempt to cover exceptions.

---
 .../Mac/Clock SignalTests/68000OldVsNew.mm    | 48 +++++++++++++------
 1 file changed, 33 insertions(+), 15 deletions(-)

diff --git a/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm b/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm
index 6938d7d25..b08503cab 100644
--- a/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm	
+++ b/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm	
@@ -243,17 +243,17 @@ template <typename M68000> struct Tester {
 
 void print_state(FILE *target, const CPU::MC68000Mk2::State &state, const std::vector<Transaction> &transactions, bool is_initial) {
 	for(int c = 0; c < 8; c++) {
-		fprintf(target, "\"D%d\": %u, ", c, state.registers.data[c]);
+		fprintf(target, "\"d%d\": %u, ", c, state.registers.data[c]);
 	}
 
 	for(int c = 0; c < 7; c++) {
-		fprintf(target, "\"A%d\": %u, ", c, state.registers.address[c]);
+		fprintf(target, "\"a%d\": %u, ", c, state.registers.address[c]);
 	}
 
-	fprintf(target, "\"USP\": %u, ", state.registers.user_stack_pointer);
-	fprintf(target, "\"SSP\": %u, ", state.registers.supervisor_stack_pointer);
-	fprintf(target, "\"SR\": %u, ", state.registers.status);
-	fprintf(target, "\"PC\": %u, ", state.registers.program_counter - 4);
+	fprintf(target, "\"usp\": %u, ", state.registers.user_stack_pointer);
+	fprintf(target, "\"ssp\": %u, ", state.registers.supervisor_stack_pointer);
+	fprintf(target, "\"sr\": %u, ", state.registers.status);
+	fprintf(target, "\"pc\": %u, ", state.registers.program_counter - 4);
 
 	fprintf(target, "\"prefetch\": [%u, %u], ", state.prefetch[0], state.prefetch[1]);
 
@@ -358,6 +358,9 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
 			} else {
 				fprintf(target, "\"t\", ");
 			}
+
+			// Include next in the calculation of time below.
+			++next;
 		}
 		HalfCycles length;
 		if(next == transactions.end()) {
@@ -368,6 +371,10 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
 		fprintf(target, "%d", length.as<int>() >> 1);
 
 		if(is_access) {
+			// Undo the 'move to one after' step that allowed next to be included
+			// in this transaction's cycle count.
+			--next;
+
 			fprintf(target, ", %d, ", iterator->function_code);
 			fprintf(target, "%d, ", iterator->address & 0xff'ffff);
 
@@ -412,7 +419,7 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
 			continue;
 		}
 
-		const auto operation = to_string(instruction.operation);
+		const auto operation = instruction.operation_string();
 		opcodesByOperation[operation].push_back(c);
 	}
 
@@ -430,6 +437,7 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
 		NSString *const targetName = [NSString stringWithFormat:@"%@%s.json", tempDir, pair.first];
 		FILE *const target = fopen(targetName.UTF8String, "wt");
 
+		const bool force_addresses_even = decoder.decode(pair.second[0]).operation == InstructionSet::M68k::Operation::UNLINK;
 		bool is_first_test = true;
 		fprintf(target, "[");
 
@@ -448,24 +456,34 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
 			// Generate a random initial register state.
 			auto initialState = tester->processor.get_state();
 
+			// Require address pointers to be even 99% of the time, or always for UNLINK.
+			const bool addresses_are_even = (rand() >= int(float(RAND_MAX) * 0.99f)) || force_addresses_even;
 			for(int c = 0; c < 8; c++) {
 				initialState.registers.data[c] = rand() ^ (rand() << 1);
-				if(c != 7) initialState.registers.address[c] = rand() << 1;
+				if(c != 7) {
+					initialState.registers.address[c] = rand() ^ (rand() << 1);
+					if(addresses_are_even) initialState.registers.address[c] &= ~1;
+				}
 			}
-			// Fully to paper over the two 68000s' different ways of doing a faked
-			// reset, pick a random status such that:
+
+			// Pick a random status such that:
 			//
-			//	(i) supervisor mode is active;
+			//	(i) supervisor mode is active 99% of the time;
 			//	(ii) trace is inactive; and
 			//	(iii) interrupt level is 7.
-			initialState.registers.status = (rand() | (1 << 13) | (7 << 8)) & ~(1 << 15);
+			const bool is_supervisor = rand() >= int(float(RAND_MAX) * 0.99f);
+			initialState.registers.status = (rand() | (int(is_supervisor) << 13) | (7 << 8)) & ~(1 << 15);
 			initialState.registers.user_stack_pointer = rand() << 1;
-			initialState.registers.supervisor_stack_pointer = 0x800;
+			initialState.registers.supervisor_stack_pointer = rand() << 1;
 
 			// Set state.
 			tester->processor.set_state(initialState);
 
-			// Run a single instruction.
+			// Run for zero instructions to grab the real initial state (i.e. valid prefetch, ssp, etc).
+			tester->run_instructions(0);
+			auto populatedInitialState = tester->processor.get_state();
+
+			// Run for another instruction to do the actual work.
 			tester->run_instructions(1);
 
 			const auto finalState = tester->processor.get_state();
@@ -473,7 +491,7 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
 			// Output initial state.
 			fprintf(target, "{ \"name\": \"%04x [%s] %d\", ", opcode, decoder.decode(opcode).to_string().c_str(), test + 1);
 			fprintf(target, "\"initial\": {");
-			print_state(target, initialState, tester->bus_handler.transactions, true);
+			print_state(target, populatedInitialState, tester->bus_handler.transactions, true);
 
 			// Output final state.
 			fprintf(target, "}, \"final\": {");

From b848b1389a07cd9eff4b0feec84edf083bb285bf Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Tue, 6 Sep 2022 15:08:35 -0400
Subject: [PATCH 10/12] Include gaps in captured transactions, better collect
 final RAM state.

---
 .../Mac/Clock SignalTests/68000OldVsNew.mm    | 20 +++++++++----------
 1 file changed, 9 insertions(+), 11 deletions(-)

diff --git a/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm b/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm
index b08503cab..1724c114a 100644
--- a/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm	
+++ b/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm	
@@ -163,7 +163,7 @@ struct BusHandler {
 		}
 
 		// Push back only if interesting.
-		if(transaction.address_strobe || transaction.data_strobes || transaction.function_code == 7) {
+		if(capture_all_transactions || transaction.address_strobe || transaction.data_strobes || transaction.function_code == 7) {
 			if(transaction_delay) {
 				--transaction_delay;
 
@@ -181,6 +181,7 @@ struct BusHandler {
 
 	int transaction_delay;
 	int instructions;
+	bool capture_all_transactions = false;
 
 	HalfCycles time;
 	std::vector<Transaction> transactions;
@@ -298,21 +299,14 @@ void print_state(FILE *target, const CPU::MC68000Mk2::State &state, const std::v
 			switch(transaction.data_strobes) {
 				default: continue;
 				case 1:
-					if(ram.find(transaction.address) == ram.end()) {
-						ram[transaction.address] = transaction.value;
-					}
+					ram[transaction.address] = transaction.value;
 				break;
 				case 2:
-					if(ram.find(transaction.address) == ram.end()) {
-						ram[transaction.address] = uint8_t(transaction.value >> 8);
-					}
-					if(ram.find(transaction.address+1) == ram.end()) {
-						ram[transaction.address+1] = uint8_t(transaction.value);
-					}
+					ram[transaction.address] = uint8_t(transaction.value >> 8);
+					ram[transaction.address+1] = uint8_t(transaction.value);
 				break;
 			}
 		}
-
 	}
 
 	bool is_first = true;
@@ -406,6 +400,7 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
 	InstructionSet::M68k::Predecoder<InstructionSet::M68k::Model::M68000> decoder;
 	RandomStore random_store;
 	auto tester = std::make_unique<Tester<NewProcessor>>(random_store, 0x02);
+	tester->bus_handler.capture_all_transactions = true;
 
 	// Bucket opcodes by operation.
 	std::unordered_map<const char *, std::vector<uint16_t>> opcodesByOperation;
@@ -480,8 +475,11 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
 			tester->processor.set_state(initialState);
 
 			// Run for zero instructions to grab the real initial state (i.e. valid prefetch, ssp, etc).
+			// Then make sure no transactions or time carry over into the actual instruction.
 			tester->run_instructions(0);
 			auto populatedInitialState = tester->processor.get_state();
+			tester->bus_handler.transactions.clear();
+			tester->bus_handler.time = HalfCycles(0);
 
 			// Run for another instruction to do the actual work.
 			tester->run_instructions(1);

From de8ce3380c2a41d29d9bc158d5508eea56016dfb Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Tue, 6 Sep 2022 20:49:45 -0400
Subject: [PATCH 11/12] Record only 8 bits for byte accesses.

---
 OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm b/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm
index 1724c114a..6802f971d 100644
--- a/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm	
+++ b/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm	
@@ -374,11 +374,9 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
 
 			switch(next->data_strobes) {
 				default: assert(false);
-				case 1:	fprintf(target, "\".b\", ");	break;
-				case 2: fprintf(target, "\".w\", "); 	break;
-				break;
+				case 1:	fprintf(target, "\".b\", %d", next->value & 0xff);	break;
+				case 2:	fprintf(target, "\".w\", %d", next->value);			break;
 			}
-			fprintf(target, "%d", next->value);
 
 			++next;
 		}

From dad1d7744e1a50d447e5e0fe32c01633c7e0c9af Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Thu, 8 Sep 2022 16:41:10 -0400
Subject: [PATCH 12/12] Disable test generation.

---
 OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm b/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm
index 6802f971d..6d0655f2e 100644
--- a/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm	
+++ b/OSBindings/Mac/Clock SignalTests/68000OldVsNew.mm	
@@ -393,7 +393,8 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
 
 @implementation M68000OldVsNewTests
 
-- (void)testGenerate {
+//- (void)testGenerate {
+- (void)generate {
 	srand(68000);
 	InstructionSet::M68k::Predecoder<InstructionSet::M68k::Model::M68000> decoder;
 	RandomStore random_store;
@@ -555,7 +556,7 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
 	int testsRun = 0;
 	std::set<InstructionSet::M68k::Operation> failing_operations;
 	for(int c = 0; c < 65536; c++) {
-//		printf("%04x\n", c);
+		printf("%04x\n", c);
 
 		// Test only defined opcodes that aren't STOP (which will never teminate).
 		const auto instruction = decoder.decode(uint16_t(c));