m68000-python 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,354 @@
1
+ """Disassemble 68000 code, from the same opcode map the core dispatches on.
2
+
3
+ ``disassemble(read_word, address)`` decodes the instruction at ``address``
4
+ and returns an :class:`Instruction`: its address, its words, and its text.
5
+ Which instruction a first word is comes from ``_dispatch.NAMES``, the table
6
+ the core itself runs, so the disassembler and the core cannot disagree about
7
+ what a word means. The text follows MAME's 68000 disassembler (m68kdasm.cpp
8
+ at mame0285: lowercase mnemonics with a size suffix, ``$`` hex, ``D0``/``A0``
9
+ registers, PC-relative operands shown as their target address), so a
10
+ disassembly can be diffed against a MAME trace line by line.
11
+ """
12
+
13
+ from collections.abc import Callable
14
+ from dataclasses import dataclass
15
+
16
+ from m68000_python._dispatch import NAMES
17
+ from m68000_python._flags import CONDITION_NAMES
18
+
19
+ WordReader = Callable[[int], int]
20
+
21
+ SIZE_SUFFIX = {0: ".b", 1: ".w", 2: ".l"}
22
+ MOVE_SUFFIX = {1: ".b", 3: ".w", 2: ".l"}
23
+
24
+
25
+ @dataclass(frozen=True, slots=True)
26
+ class Instruction:
27
+ """One disassembled instruction."""
28
+
29
+ address: int
30
+ words: tuple[int, ...]
31
+ mnemonic: str
32
+ operands: tuple[str, ...]
33
+
34
+ @property
35
+ def length(self) -> int:
36
+ return 2 * len(self.words)
37
+
38
+ @property
39
+ def data(self) -> bytes:
40
+ """The instruction's bytes, big-endian."""
41
+ return b"".join(word.to_bytes(2, "big") for word in self.words)
42
+
43
+ @property
44
+ def text(self) -> str:
45
+ if not self.operands:
46
+ return self.mnemonic
47
+ return f"{self.mnemonic} {', '.join(self.operands)}"
48
+
49
+ def __str__(self) -> str:
50
+ return self.text
51
+
52
+
53
+ def _signed_hex_8(value: int) -> str:
54
+ value &= 0xFF
55
+ if value & 0x80:
56
+ return f"-${(0x100 - value):x}"
57
+ return f"${value:x}"
58
+
59
+
60
+ def _signed_hex_16(value: int) -> str:
61
+ value &= 0xFFFF
62
+ if value & 0x8000:
63
+ return f"-${(0x10000 - value):x}"
64
+ return f"${value:x}"
65
+
66
+
67
+ def _signed_hex_32(value: int) -> str:
68
+ value &= 0xFFFFFFFF
69
+ if value & 0x80000000:
70
+ return f"-${(0x100000000 - value):x}"
71
+ return f"${value:x}"
72
+
73
+
74
+ def _sign_16(value: int) -> int:
75
+ return value - 0x10000 if value & 0x8000 else value
76
+
77
+
78
+ class _Reader:
79
+ """Reads the words after the opcode, remembering them."""
80
+
81
+ def __init__(self, read_word: WordReader, address: int) -> None:
82
+ self.read_word = read_word
83
+ self.pc = address + 2
84
+ self.words: list[int] = []
85
+
86
+ def word(self) -> int:
87
+ value = self.read_word(self.pc & 0xFFFFFF) & 0xFFFF
88
+ self.words.append(value)
89
+ self.pc += 2
90
+ return value
91
+
92
+ def long(self) -> int:
93
+ high = self.word()
94
+ return (high << 16) | self.word()
95
+
96
+ def ea(self, field: int, size: int) -> str:
97
+ """Format the operand of a 6-bit EA field; ``size`` 0/1/2 for immediates."""
98
+ mode, register = field >> 3, field & 7
99
+ if mode == 0:
100
+ return f"D{register}"
101
+ if mode == 1:
102
+ return f"A{register}"
103
+ if mode == 2:
104
+ return f"(A{register})"
105
+ if mode == 3:
106
+ return f"(A{register})+"
107
+ if mode == 4:
108
+ return f"-(A{register})"
109
+ if mode == 5:
110
+ return f"({_signed_hex_16(self.word())},A{register})"
111
+ if mode == 6:
112
+ return self._index(f"A{register}")
113
+ if register == 0:
114
+ return f"${self.word():x}.w"
115
+ if register == 1:
116
+ return f"${self.long():x}.l"
117
+ if register == 2:
118
+ base = self.pc
119
+ return f"(${(base + _sign_16(self.word())) & 0xFFFFFFFF:x},PC)"
120
+ if register == 3:
121
+ return self._index("PC")
122
+ if register == 4:
123
+ if size == 0:
124
+ return f"#${self.word() & 0xFF:x}"
125
+ if size == 1:
126
+ return f"#${self.word():x}"
127
+ return f"#${self.long():x}"
128
+ return "?"
129
+
130
+ def _index(self, base: str) -> str:
131
+ extension = self.word()
132
+ kind = "A" if extension & 0x8000 else "D"
133
+ register = (extension >> 12) & 7
134
+ width = "l" if extension & 0x0800 else "w"
135
+ displacement = extension & 0xFF
136
+ if displacement:
137
+ return f"({_signed_hex_8(displacement)},{base},{kind}{register}.{width})"
138
+ return f"({base},{kind}{register}.{width})"
139
+
140
+
141
+ def _register_list(mask: int, reverse: bool) -> str:
142
+ """MOVEM's register mask as ``D0-D3/A6``; reversed for -(An) (PRM 4, MOVEM)."""
143
+ if reverse:
144
+ mask = int(f"{mask:016b}"[::-1], 2)
145
+ parts = []
146
+ for bank, offset in (("D", 0), ("A", 8)):
147
+ index = 0
148
+ while index < 8:
149
+ if mask & (1 << (offset + index)):
150
+ start = index
151
+ while index + 1 < 8 and mask & (1 << (offset + index + 1)):
152
+ index += 1
153
+ if start == index:
154
+ parts.append(f"{bank}{start}")
155
+ else:
156
+ parts.append(f"{bank}{start}-{bank}{index}")
157
+ index += 1
158
+ return "/".join(parts)
159
+
160
+
161
+ QUICK = (8, 1, 2, 3, 4, 5, 6, 7)
162
+
163
+
164
+ def _decode(name: str, opcode: int, reader: _Reader) -> tuple[str, tuple[str, ...]]:
165
+ """Mnemonic and operands of one instruction; extension words come from ``reader``."""
166
+ size_bits = (opcode >> 6) & 3
167
+ suffix = SIZE_SUFFIX.get(size_bits, "")
168
+ ea = opcode & 0x3F
169
+ rx = (opcode >> 9) & 7
170
+ ry = opcode & 7
171
+ cc = CONDITION_NAMES[(opcode >> 8) & 0xF].lower()
172
+
173
+ if name in ("subi", "addi", "cmpi"):
174
+ # Arithmetic immediates print signed, logical ones unsigned (MAME's
175
+ # get_imm_str_s / get_imm_str_u).
176
+ if size_bits == 0:
177
+ immediate = "#" + _signed_hex_8(reader.word())
178
+ elif size_bits == 1:
179
+ immediate = "#" + _signed_hex_16(reader.word())
180
+ else:
181
+ immediate = "#" + _signed_hex_32(reader.long())
182
+ return name + suffix, (immediate, reader.ea(ea, size_bits))
183
+ if name in ("ori", "andi", "eori"):
184
+ immediate = reader.ea(0x3C, size_bits)
185
+ return name + suffix, (immediate, reader.ea(ea, size_bits))
186
+ if name.endswith(("_to_ccr", "_to_sr")) and name[:4] in ("ori_", "andi", "eori"):
187
+ base = name.split("_")[0]
188
+ target = "CCR" if name.endswith("ccr") else "SR"
189
+ width = 0 if target == "CCR" else 1
190
+ return base, (reader.ea(0x3C, width), target)
191
+ if name in ("btst", "bchg", "bclr", "bset"):
192
+ if opcode & 0x0100:
193
+ return name, (f"D{rx}", reader.ea(ea, 0))
194
+ return name, (f"#${reader.word() & 0xFF:x}", reader.ea(ea, 0))
195
+ if name == "movep":
196
+ displacement = _signed_hex_16(reader.word())
197
+ width = ".l" if opcode & 0x40 else ".w"
198
+ if opcode & 0x80:
199
+ return "movep" + width, (f"D{rx}", f"({displacement},A{ry})")
200
+ return "movep" + width, (f"({displacement},A{ry})", f"D{rx}")
201
+ if name == "move":
202
+ size = MOVE_SUFFIX[opcode >> 12]
203
+ source = reader.ea(ea, {".b": 0, ".w": 1, ".l": 2}[size])
204
+ destination = reader.ea(((opcode >> 3) & 0x38) | rx, 0)
205
+ return "move" + size, (source, destination)
206
+ if name == "movea":
207
+ size = ".w" if opcode >> 12 == 3 else ".l"
208
+ return "movea" + size, (reader.ea(ea, 1 if size == ".w" else 2), f"A{rx}")
209
+ if name == "move_from_sr":
210
+ return "move", ("SR", reader.ea(ea, 1))
211
+ if name == "move_to_ccr":
212
+ return "move", (reader.ea(ea, 1), "CCR")
213
+ if name == "move_to_sr":
214
+ return "move", (reader.ea(ea, 1), "SR")
215
+ if name in ("negx", "clr", "neg", "not", "tst"):
216
+ return name + suffix, (reader.ea(ea, size_bits),)
217
+ if name in ("nbcd", "tas", "pea", "jsr", "jmp"):
218
+ return name, (reader.ea(ea, 2),)
219
+ if name == "swap":
220
+ return "swap", (f"D{ry}",)
221
+ if name == "ext":
222
+ return "ext.l" if opcode & 0x40 else "ext.w", (f"D{ry}",)
223
+ if name == "movem":
224
+ width = ".l" if opcode & 0x40 else ".w"
225
+ mask = reader.word()
226
+ if opcode & 0x0400:
227
+ return "movem" + width, (reader.ea(ea, 1), _register_list(mask, False))
228
+ predecrement = (ea >> 3) == 4
229
+ return "movem" + width, (_register_list(mask, predecrement), reader.ea(ea, 1))
230
+ if name == "trap":
231
+ return "trap", (f"#${opcode & 0xF:x}",)
232
+ if name == "link":
233
+ return "link", (f"A{ry}", f"#{_signed_hex_16(reader.word())}")
234
+ if name == "unlk":
235
+ return "unlk", (f"A{ry}",)
236
+ if name == "move_usp":
237
+ if opcode & 8:
238
+ return "move", ("USP", f"A{ry}")
239
+ return "move", (f"A{ry}", "USP")
240
+ if name == "stop":
241
+ return "stop", ("#" + _signed_hex_16(reader.word()),)
242
+ if name in ("reset", "nop", "rte", "rts", "trapv", "rtr"):
243
+ return name, ()
244
+ if name == "illegal" and opcode == 0x4AFC:
245
+ return "illegal", () # the one word that is ILLEGAL by name (PRM 4-107)
246
+ if name == "chk":
247
+ return "chk.w", (reader.ea(ea, 1), f"D{rx}")
248
+ if name == "lea":
249
+ return "lea", (reader.ea(ea, 2), f"A{rx}")
250
+ if name in ("addq", "subq"):
251
+ return name + suffix, (f"#{QUICK[rx]}", reader.ea(ea, size_bits))
252
+ if name == "dbcc":
253
+ base = reader.pc
254
+ target = (base + _sign_16(reader.word())) & 0xFFFFFFFF
255
+ # DBF is written DBRA, the assembler's name for it, as MAME writes it.
256
+ return "dbra" if cc == "f" else f"db{cc}", (f"D{ry}", f"${target:x}")
257
+ if name == "scc":
258
+ return f"s{cc}", (reader.ea(ea, 0),)
259
+ if name in ("bra", "bsr", "bcc"):
260
+ mnemonic = name if name != "bcc" else f"b{cc}"
261
+ base = reader.pc
262
+ displacement = opcode & 0xFF
263
+ if displacement:
264
+ offset = displacement - 0x100 if displacement & 0x80 else displacement
265
+ return mnemonic, (f"${(base + offset) & 0xFFFFFFFF:x}",)
266
+ return mnemonic, (f"${(base + _sign_16(reader.word())) & 0xFFFFFFFF:x}",)
267
+ if name == "moveq":
268
+ return "moveq", (f"#{_signed_hex_8(opcode)}", f"D{rx}")
269
+ if name in ("divu", "divs", "mulu", "muls"):
270
+ return name + ".w", (reader.ea(ea, 1), f"D{rx}")
271
+ if name in ("sbcd", "abcd", "addx", "subx"):
272
+ width = suffix if name in ("addx", "subx") else ""
273
+ if opcode & 8:
274
+ return name + width, (f"-(A{ry})", f"-(A{rx})")
275
+ return name + width, (f"D{ry}", f"D{rx}")
276
+ if name in ("or", "and", "sub", "add", "eor", "cmp"):
277
+ if opcode & 0x0100 and name != "cmp":
278
+ return name + suffix, (f"D{rx}", reader.ea(ea, size_bits))
279
+ return name + suffix, (reader.ea(ea, size_bits), f"D{rx}")
280
+ if name in ("suba", "adda", "cmpa"):
281
+ width = ".l" if opcode & 0x0100 else ".w"
282
+ return name + width, (reader.ea(ea, 2 if width == ".l" else 1), f"A{rx}")
283
+ if name == "cmpm":
284
+ return "cmpm" + suffix, (f"(A{ry})+", f"(A{rx})+")
285
+ if name == "exg":
286
+ mode = (opcode >> 3) & 0x1F
287
+ if mode == 0x08:
288
+ return "exg", (f"D{rx}", f"D{ry}")
289
+ if mode == 0x09:
290
+ return "exg", (f"A{rx}", f"A{ry}")
291
+ return "exg", (f"D{rx}", f"A{ry}")
292
+ if name.endswith("_memory"):
293
+ base = name.split("d_")[0]
294
+ direction = "l" if opcode & 0x0100 else "r"
295
+ return f"{base}{direction}.w", (reader.ea(ea, 1),)
296
+ if name in ("asd", "lsd", "roxd", "rod"):
297
+ base = name[:-1]
298
+ direction = "l" if opcode & 0x0100 else "r"
299
+ count = f"D{rx}" if opcode & 0x20 else f"#{QUICK[rx]}"
300
+ return f"{base}{direction}{suffix}", (count, f"D{ry}")
301
+ if name == "line_a":
302
+ return "dc.w", (f"${opcode:04x}; opcode 1010",)
303
+ if name == "line_f":
304
+ return "dc.w", (f"${opcode:04x}; opcode 1111",)
305
+ return "dc.w", (f"${opcode:04x}; ILLEGAL",)
306
+
307
+
308
+ def disassemble(read_word: WordReader, address: int) -> Instruction:
309
+ """Disassemble the instruction at ``address``; ``read_word`` reads memory."""
310
+ opcode = read_word(address & 0xFFFFFF) & 0xFFFF
311
+ reader = _Reader(read_word, address)
312
+ mnemonic, operands = _decode(NAMES[opcode], opcode, reader)
313
+ return Instruction(address, (opcode, *reader.words), mnemonic, operands)
314
+
315
+
316
+ def disassemble_bytes(data: bytes, address: int = 0) -> Instruction:
317
+ """Disassemble one instruction from ``data`` (big-endian) placed at ``address``.
318
+
319
+ Raises ValueError unless ``data`` holds exactly that one instruction.
320
+ """
321
+ if len(data) % 2 or not data:
322
+ raise ValueError("instruction data must be a non-empty whole number of words")
323
+ # Keyed by the 24-bit address the disassembler reads at: the bus has A23-A1 only.
324
+ words = {(address + i) & 0xFFFFFF: (data[i] << 8) | data[i + 1] for i in range(0, len(data), 2)}
325
+
326
+ def read_word(where: int) -> int:
327
+ if where not in words:
328
+ raise ValueError("instruction data is shorter than the instruction")
329
+ return words[where]
330
+
331
+ instruction = disassemble(read_word, address)
332
+ if instruction.length != len(data):
333
+ raise ValueError("instruction data is longer than the instruction")
334
+ return instruction
335
+
336
+
337
+ def disassemble_range(read_word: WordReader, start: int, end: int) -> list[Instruction]:
338
+ """Disassemble every instruction from ``start`` up to (not including) ``end``."""
339
+ result = []
340
+ address = start
341
+ while address < end:
342
+ instruction = disassemble(read_word, address)
343
+ result.append(instruction)
344
+ address += instruction.length
345
+ return result
346
+
347
+
348
+ __all__ = [
349
+ "Instruction",
350
+ "WordReader",
351
+ "disassemble",
352
+ "disassemble_bytes",
353
+ "disassemble_range",
354
+ ]
m68000_python/py.typed ADDED
File without changes
m68000_python/state.py ADDED
@@ -0,0 +1,76 @@
1
+ """Immutable values for capturing and restoring MC68000 processor state."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ def _require_int(name: str, value: object, maximum: int) -> None:
7
+ if type(value) is not int or not 0 <= value <= maximum:
8
+ raise ValueError(f"{name} must be an integer in range 0..{maximum:#x}")
9
+
10
+
11
+ def _require_bool(name: str, value: object) -> None:
12
+ if type(value) is not bool:
13
+ raise ValueError(f"{name} must be a bool")
14
+
15
+
16
+ @dataclass(frozen=True, slots=True)
17
+ class CPUState:
18
+ """Complete CPU-owned state at an instruction boundary.
19
+
20
+ Registers, the prefetch queue, the interrupt input as the core last saw
21
+ it, and the internal state that decides what the next ``step()`` does.
22
+ Host memory, devices and scheduling are excluded: restoring this value
23
+ restores the processor, not a machine.
24
+
25
+ ``pc`` is the address of the instruction in ``ir``, always even (a jump
26
+ to an odd address faults before it becomes PC); the queue has read
27
+ ``irc`` from ``pc + 2`` and will next read ``pc + 4`` (the SingleStepTests
28
+ corpus's ``pc``). Stopped by STOP, ``pc`` is the STOP's own address and
29
+ execution resumes at ``pc + 4``.
30
+ """
31
+
32
+ d: tuple[int, ...] = (0,) * 8
33
+ a: tuple[int, ...] = (0,) * 7
34
+ usp: int = 0
35
+ ssp: int = 0
36
+ sr: int = 0x2700
37
+ pc: int = 0
38
+ ir: int = 0
39
+ irc: int = 0
40
+ # Inputs, as the host has set them.
41
+ ipl: int = 0
42
+ # Internal state.
43
+ nmi_edge: bool = False # a level 7 edge seen and not yet taken
44
+ trace_pending: bool = False # the last instruction ran with T set
45
+ stopped: bool = False # inside STOP
46
+ halted: bool = False # double bus fault: only reset leaves it
47
+ clock: int = 0 # clocks run so far (the E-clock phase comes from it)
48
+
49
+ def __post_init__(self) -> None:
50
+ if len(self.d) != 8 or len(self.a) != 7:
51
+ raise ValueError("d must hold 8 values and a 7 (A7 is usp or ssp)")
52
+ for index, value in enumerate(self.d):
53
+ _require_int(f"d{index}", value, 0xFFFFFFFF)
54
+ for index, value in enumerate(self.a):
55
+ _require_int(f"a{index}", value, 0xFFFFFFFF)
56
+ for name in ("usp", "ssp", "pc"):
57
+ _require_int(name, getattr(self, name), 0xFFFFFFFF)
58
+ if self.pc & 1:
59
+ raise ValueError("pc must be even: instructions live at even addresses")
60
+ for name in ("ir", "irc"):
61
+ _require_int(name, getattr(self, name), 0xFFFF)
62
+ _require_int("sr", self.sr, 0xFFFF)
63
+ if self.sr & ~0xA71F:
64
+ raise ValueError("sr bits 14, 12, 11 and 7-5 read as zero on the 68000")
65
+ _require_int("ipl", self.ipl, 7)
66
+ _require_int("clock", self.clock, (1 << 64) - 1)
67
+ for name in ("nmi_edge", "trace_pending", "stopped", "halted"):
68
+ _require_bool(name, getattr(self, name))
69
+
70
+ @property
71
+ def a7(self) -> int:
72
+ """The active stack pointer: ``ssp`` in supervisor mode, else ``usp``."""
73
+ return self.ssp if self.sr & 0x2000 else self.usp
74
+
75
+
76
+ __all__ = ["CPUState"]