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.
- m68000_python/__init__.py +73 -0
- m68000_python/__main__.py +112 -0
- m68000_python/_alu.py +656 -0
- m68000_python/_bcd.py +137 -0
- m68000_python/_bits.py +100 -0
- m68000_python/_control.py +208 -0
- m68000_python/_core.py +545 -0
- m68000_python/_dispatch.py +243 -0
- m68000_python/_ea.py +177 -0
- m68000_python/_flags.py +152 -0
- m68000_python/_loads.py +405 -0
- m68000_python/_shifts.py +168 -0
- m68000_python/_system.py +314 -0
- m68000_python/conformance.py +796 -0
- m68000_python/console.py +254 -0
- m68000_python/cpu.py +373 -0
- m68000_python/debug.py +361 -0
- m68000_python/disasm.py +354 -0
- m68000_python/py.typed +0 -0
- m68000_python/state.py +76 -0
- m68000_python/trace.py +296 -0
- m68000_python-0.1.0.dist-info/METADATA +379 -0
- m68000_python-0.1.0.dist-info/RECORD +25 -0
- m68000_python-0.1.0.dist-info/WHEEL +4 -0
- m68000_python-0.1.0.dist-info/licenses/LICENSE +21 -0
m68000_python/disasm.py
ADDED
|
@@ -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"]
|