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/_core.py
ADDED
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
"""Processor state, the bus, the prefetch queue, and exception entry.
|
|
2
|
+
|
|
3
|
+
Everything a handler touches goes through the helpers here: sized register
|
|
4
|
+
access, the host bus with its odd-address check, the two-word prefetch queue,
|
|
5
|
+
and the group 0/1/2 exception sequences. Sources are cited as in
|
|
6
|
+
docs/start-here.md: PRM is the M68000 Family Programmer's Reference Manual,
|
|
7
|
+
UM the M68000 User's Manual. Where the manuals are silent, the order of bus
|
|
8
|
+
accesses, the internal ("idle") clocks, and the value an address error
|
|
9
|
+
stacks as PC follow the pinned SingleStepTests/m68000 corpus, which records
|
|
10
|
+
them as MAME 0.285's microcode-transcribed core produces them (tier T3; see
|
|
11
|
+
docs/undocumented-behavior.md).
|
|
12
|
+
|
|
13
|
+
Time is counted in clocks as the core goes: every bus access adds 4 and every
|
|
14
|
+
internal step adds the clocks the microcode spends there, so an instruction
|
|
15
|
+
that faults halfway has spent exactly the clocks up to the fault.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from collections.abc import Callable
|
|
19
|
+
|
|
20
|
+
# -- status register (PRM 1.3.1) ----------------------------------------------
|
|
21
|
+
T = 0x8000 # trace
|
|
22
|
+
S = 0x2000 # supervisor
|
|
23
|
+
IPL_MASK = 0x0700 # interrupt priority mask, I2-I0
|
|
24
|
+
X = 0x10 # extend
|
|
25
|
+
N = 0x08 # negative
|
|
26
|
+
Z = 0x04 # zero
|
|
27
|
+
V = 0x02 # overflow
|
|
28
|
+
C = 0x01 # carry
|
|
29
|
+
CCR_BITS = 0x1F
|
|
30
|
+
#: Bits 14, 12, 11, 7, 6 and 5 read as zero on the 68000 (PRM 1.3.1).
|
|
31
|
+
SR_BITS = 0xA71F
|
|
32
|
+
|
|
33
|
+
MASK24 = 0xFFFFFF # the 68000 drives A23-A1 (UM 3.1)
|
|
34
|
+
|
|
35
|
+
# Function codes FC2-FC0 (UM Table 3-2).
|
|
36
|
+
FC_USER_DATA = 1
|
|
37
|
+
FC_USER_PROGRAM = 2
|
|
38
|
+
FC_SUPERVISOR_DATA = 5
|
|
39
|
+
FC_SUPERVISOR_PROGRAM = 6
|
|
40
|
+
|
|
41
|
+
# Exception vectors (UM Table 6-2).
|
|
42
|
+
VECTOR_BUS_ERROR = 2
|
|
43
|
+
VECTOR_ADDRESS_ERROR = 3
|
|
44
|
+
VECTOR_ILLEGAL = 4
|
|
45
|
+
VECTOR_ZERO_DIVIDE = 5
|
|
46
|
+
VECTOR_CHK = 6
|
|
47
|
+
VECTOR_TRAPV = 7
|
|
48
|
+
VECTOR_PRIVILEGE = 8
|
|
49
|
+
VECTOR_TRACE = 9
|
|
50
|
+
VECTOR_LINE_A = 10
|
|
51
|
+
VECTOR_LINE_F = 11
|
|
52
|
+
VECTOR_UNINITIALIZED = 15
|
|
53
|
+
VECTOR_SPURIOUS = 24
|
|
54
|
+
VECTOR_AUTOVECTOR_BASE = 24 # level n autovectors through vector 24 + n
|
|
55
|
+
VECTOR_TRAP_BASE = 32
|
|
56
|
+
|
|
57
|
+
#: ``acknowledge(level)`` answers: a vector number 0-255, or one of these.
|
|
58
|
+
AUTOVECTOR = -1
|
|
59
|
+
SPURIOUS = -2
|
|
60
|
+
|
|
61
|
+
#: Operand sizes in bytes (1, 2, 4) index their masks and sign bits: tuples,
|
|
62
|
+
#: because a tuple index is cheaper than a dict lookup on every instruction
|
|
63
|
+
#: (rung A of the speed ladder, docs/validation.md "Speed").
|
|
64
|
+
MASK = (0, 0xFF, 0xFFFF, 0, 0xFFFFFFFF)
|
|
65
|
+
MSB = (0, 0x80, 0x8000, 0, 0x80000000)
|
|
66
|
+
|
|
67
|
+
ReadFunction = Callable[..., int]
|
|
68
|
+
WriteFunction = Callable[..., None]
|
|
69
|
+
Acknowledge = Callable[[int], int]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class BusError(Exception):
|
|
73
|
+
"""Raise from a host read or write callable to assert BERR on that access.
|
|
74
|
+
|
|
75
|
+
The core catches it at the access, aborts the instruction, and takes the
|
|
76
|
+
bus-error exception (vector 2) with the seven-word group 0 frame
|
|
77
|
+
(UM 6.3.9.1). Most hosts never raise it.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class GroupZero(Exception):
|
|
82
|
+
"""Internal: an address or bus error aborting the current instruction."""
|
|
83
|
+
|
|
84
|
+
def __init__(self, vector: int, address: int, write: bool, program: bool) -> None:
|
|
85
|
+
super().__init__(vector, address, write, program)
|
|
86
|
+
self.vector = vector
|
|
87
|
+
self.address = address # the full 32-bit value the microcode computed
|
|
88
|
+
self.write = write
|
|
89
|
+
self.program = program
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class CoreMixin:
|
|
93
|
+
"""Private implementation of the state and helpers every handler uses."""
|
|
94
|
+
|
|
95
|
+
# -- construction -------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
def _init_core(
|
|
98
|
+
self,
|
|
99
|
+
read_byte: ReadFunction,
|
|
100
|
+
read_word: ReadFunction,
|
|
101
|
+
write_byte: WriteFunction,
|
|
102
|
+
write_word: WriteFunction,
|
|
103
|
+
acknowledge: Acknowledge | None,
|
|
104
|
+
function_codes: bool,
|
|
105
|
+
tas_write: WriteFunction | None,
|
|
106
|
+
address_error: Callable[[int, bool, int], None] | None,
|
|
107
|
+
reset_devices: Callable[[], None] | None,
|
|
108
|
+
) -> None:
|
|
109
|
+
# R[0..7] are D0-D7, R[8..15] are A0-A7; A7 is the active stack
|
|
110
|
+
# pointer and the inactive one waits in _other_sp (PRM 1.3).
|
|
111
|
+
self.R = [0] * 16
|
|
112
|
+
self._other_sp = 0
|
|
113
|
+
# Undefined at reset on silicon apart from S, T and the mask (UM 6.3.1).
|
|
114
|
+
self.SR = S | IPL_MASK
|
|
115
|
+
# The prefetch queue (UM "Prefetch", docs/start-here.md): ir is the
|
|
116
|
+
# opcode word of the next instruction, irc the word after it, and
|
|
117
|
+
# _pc the address the next queue refill reads, which is the
|
|
118
|
+
# instruction's address + 4 at a boundary (the corpus's ``pc``).
|
|
119
|
+
self.ir = 0
|
|
120
|
+
self.irc = 0
|
|
121
|
+
self._pc = 0
|
|
122
|
+
# The microcode's PC register: an address error stacks it. It trails
|
|
123
|
+
# _pc and is brought up to it only at the steps where the microcode
|
|
124
|
+
# copies the address unit into PC (see _commit_pc).
|
|
125
|
+
self._fault_pc = 0
|
|
126
|
+
self._opcode = 0 # the instruction being executed (IRD)
|
|
127
|
+
self._cycles = 0 # clocks of the step in progress
|
|
128
|
+
#: Clocks run since construction: the sum of every step() and reset().
|
|
129
|
+
#: The host may set it; only the E-clock phase of an autovectored
|
|
130
|
+
#: interrupt acknowledge depends on it.
|
|
131
|
+
self.clock = 0
|
|
132
|
+
# Interrupts, trace and the stopped/halted states (UM 6.3).
|
|
133
|
+
self.ipl = 0 # the level on IPL2-IPL0 as the host last set it
|
|
134
|
+
self._nmi_edge = False # a 0-to-7 transition not yet taken
|
|
135
|
+
self._trace_pending = False
|
|
136
|
+
# Set by an instruction that is never executed (illegal, line A/F,
|
|
137
|
+
# privilege violation): no trace follows it (UM 6.3.8).
|
|
138
|
+
self._untraced = False
|
|
139
|
+
self._processing_exception = False
|
|
140
|
+
self.stopped = False
|
|
141
|
+
self.halted = False
|
|
142
|
+
#: E-clock phase (clock mod 10) at the last autovectored acknowledge.
|
|
143
|
+
self.last_acknowledge_phase = 0
|
|
144
|
+
|
|
145
|
+
self._acknowledge = acknowledge
|
|
146
|
+
self._address_error_hook = address_error
|
|
147
|
+
self._reset_devices = reset_devices
|
|
148
|
+
self.function_codes = function_codes
|
|
149
|
+
self.tas_write = tas_write
|
|
150
|
+
self.attach_bus(read_byte, read_word, write_byte, write_word)
|
|
151
|
+
|
|
152
|
+
def attach_bus(
|
|
153
|
+
self,
|
|
154
|
+
read_byte: ReadFunction,
|
|
155
|
+
read_word: ReadFunction,
|
|
156
|
+
write_byte: WriteFunction,
|
|
157
|
+
write_word: WriteFunction,
|
|
158
|
+
) -> None:
|
|
159
|
+
"""Give the core its four bus callables (the constructor's; a debugger's wrappers).
|
|
160
|
+
|
|
161
|
+
The originals stay readable as ``read_byte``, ``read_word``,
|
|
162
|
+
``write_byte`` and ``write_word``.
|
|
163
|
+
"""
|
|
164
|
+
for name, value in (("read_byte", read_byte), ("read_word", read_word),
|
|
165
|
+
("write_byte", write_byte), ("write_word", write_word)): # fmt: skip
|
|
166
|
+
if not callable(value):
|
|
167
|
+
raise TypeError(f"{name} must be callable")
|
|
168
|
+
self.read_byte, self.read_word = read_byte, read_word
|
|
169
|
+
self.write_byte, self.write_word = write_byte, write_word
|
|
170
|
+
tas_write = self.tas_write
|
|
171
|
+
if self.function_codes:
|
|
172
|
+
# Only a host that asks pays for function codes (README, "The embedding contract").
|
|
173
|
+
self._read_program = lambda address: read_word(address, fc=self._fc(True))
|
|
174
|
+
self._read_data_word = lambda address: read_word(address, fc=self._fc(False))
|
|
175
|
+
self._read_data_byte = lambda address: read_byte(address, fc=self._fc(False))
|
|
176
|
+
self._read_program_byte = lambda address: read_byte(address, fc=self._fc(True))
|
|
177
|
+
self._write_data_word = lambda address, value: write_word(
|
|
178
|
+
address, value, fc=self._fc(False)
|
|
179
|
+
)
|
|
180
|
+
self._write_data_byte = lambda address, value: write_byte(
|
|
181
|
+
address, value, fc=self._fc(False)
|
|
182
|
+
)
|
|
183
|
+
else:
|
|
184
|
+
self._read_program = read_word
|
|
185
|
+
self._read_data_word = read_word
|
|
186
|
+
self._read_data_byte = read_byte
|
|
187
|
+
self._read_program_byte = read_byte
|
|
188
|
+
self._write_data_word = write_word
|
|
189
|
+
self._write_data_byte = write_byte
|
|
190
|
+
if tas_write is None:
|
|
191
|
+
self._tas_write = self._write_data_byte
|
|
192
|
+
elif self.function_codes:
|
|
193
|
+
self._tas_write = lambda address, value: tas_write(address, value, fc=self._fc(False))
|
|
194
|
+
else:
|
|
195
|
+
self._tas_write = tas_write
|
|
196
|
+
|
|
197
|
+
# -- the stack pointers and the status register --------------------------
|
|
198
|
+
|
|
199
|
+
def _fc(self, program: bool) -> int:
|
|
200
|
+
if self.SR & S:
|
|
201
|
+
return FC_SUPERVISOR_PROGRAM if program else FC_SUPERVISOR_DATA
|
|
202
|
+
return FC_USER_PROGRAM if program else FC_USER_DATA
|
|
203
|
+
|
|
204
|
+
def _set_sr(self, value: int) -> None:
|
|
205
|
+
"""Write the whole SR, swapping A7 with the other stack pointer if S changes."""
|
|
206
|
+
value &= SR_BITS
|
|
207
|
+
if (value ^ self.SR) & S:
|
|
208
|
+
self.R[15], self._other_sp = self._other_sp, self.R[15]
|
|
209
|
+
self.SR = value
|
|
210
|
+
|
|
211
|
+
def _set_ccr(self, value: int) -> None:
|
|
212
|
+
self.SR = (self.SR & 0xFF00) | (value & CCR_BITS)
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def usp(self) -> int:
|
|
216
|
+
"""The user stack pointer, whichever mode the CPU is in."""
|
|
217
|
+
return self._other_sp if self.SR & S else self.R[15]
|
|
218
|
+
|
|
219
|
+
@usp.setter
|
|
220
|
+
def usp(self, value: int) -> None:
|
|
221
|
+
if self.SR & S:
|
|
222
|
+
self._other_sp = value & 0xFFFFFFFF
|
|
223
|
+
else:
|
|
224
|
+
self.R[15] = value & 0xFFFFFFFF
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
def ssp(self) -> int:
|
|
228
|
+
"""The supervisor stack pointer, whichever mode the CPU is in."""
|
|
229
|
+
return self.R[15] if self.SR & S else self._other_sp
|
|
230
|
+
|
|
231
|
+
@ssp.setter
|
|
232
|
+
def ssp(self, value: int) -> None:
|
|
233
|
+
if self.SR & S:
|
|
234
|
+
self.R[15] = value & 0xFFFFFFFF
|
|
235
|
+
else:
|
|
236
|
+
self._other_sp = value & 0xFFFFFFFF
|
|
237
|
+
|
|
238
|
+
# -- the bus ------------------------------------------------------------
|
|
239
|
+
# Every access checks alignment first: a word or long at an odd address
|
|
240
|
+
# is an address error, and the aborted cycle never reaches the host
|
|
241
|
+
# (UM 6.3.10; the corpus marks it "re"/"we", AS not asserted).
|
|
242
|
+
|
|
243
|
+
def _fault(self, vector: int, address: int, write: bool, program: bool) -> GroupZero:
|
|
244
|
+
return GroupZero(vector, address & 0xFFFFFFFF, write, program)
|
|
245
|
+
|
|
246
|
+
def _read_word(self, address: int) -> int:
|
|
247
|
+
if address & 1:
|
|
248
|
+
raise self._fault(VECTOR_ADDRESS_ERROR, address, False, False)
|
|
249
|
+
self._cycles += 4
|
|
250
|
+
try:
|
|
251
|
+
return self._read_data_word(address & MASK24)
|
|
252
|
+
except BusError:
|
|
253
|
+
raise self._fault(VECTOR_BUS_ERROR, address, False, False) from None
|
|
254
|
+
|
|
255
|
+
def _read_byte(self, address: int) -> int:
|
|
256
|
+
self._cycles += 4
|
|
257
|
+
try:
|
|
258
|
+
return self._read_data_byte(address & MASK24)
|
|
259
|
+
except BusError:
|
|
260
|
+
raise self._fault(VECTOR_BUS_ERROR, address, False, False) from None
|
|
261
|
+
|
|
262
|
+
def _read_long(self, address: int) -> int:
|
|
263
|
+
"""Two word reads, high word first (UM 2.4)."""
|
|
264
|
+
high = self._read_word(address)
|
|
265
|
+
return (high << 16) | self._read_word(address + 2)
|
|
266
|
+
|
|
267
|
+
def _write_word(self, address: int, value: int) -> None:
|
|
268
|
+
if address & 1:
|
|
269
|
+
raise self._fault(VECTOR_ADDRESS_ERROR, address, True, False)
|
|
270
|
+
self._cycles += 4
|
|
271
|
+
try:
|
|
272
|
+
self._write_data_word(address & MASK24, value & 0xFFFF)
|
|
273
|
+
except BusError:
|
|
274
|
+
raise self._fault(VECTOR_BUS_ERROR, address, True, False) from None
|
|
275
|
+
|
|
276
|
+
def _write_byte(self, address: int, value: int) -> None:
|
|
277
|
+
self._cycles += 4
|
|
278
|
+
try:
|
|
279
|
+
self._write_data_byte(address & MASK24, value & 0xFF)
|
|
280
|
+
except BusError:
|
|
281
|
+
raise self._fault(VECTOR_BUS_ERROR, address, True, False) from None
|
|
282
|
+
|
|
283
|
+
def _write_long(self, address: int, value: int) -> None:
|
|
284
|
+
"""Two word writes, high word first."""
|
|
285
|
+
if address & 1:
|
|
286
|
+
raise self._fault(VECTOR_ADDRESS_ERROR, address, True, False)
|
|
287
|
+
self._write_word(address, value >> 16)
|
|
288
|
+
self._write_word(address + 2, value)
|
|
289
|
+
|
|
290
|
+
def _write_long_low_first(self, address: int, value: int) -> None:
|
|
291
|
+
"""Two word writes, low word (at address + 2) first.
|
|
292
|
+
|
|
293
|
+
The order of -(An) destinations and of read-modify-write results:
|
|
294
|
+
the microcode works toward the high word (SST MOVE.l, ADDX.l, SUBX.l,
|
|
295
|
+
MOVEM.l and the long read-modify-write files; T3). An odd
|
|
296
|
+
address faults on the first write, at ``address + 2``.
|
|
297
|
+
"""
|
|
298
|
+
self._write_word(address + 2, value)
|
|
299
|
+
self._write_word(address, value >> 16)
|
|
300
|
+
|
|
301
|
+
def _read(self, size: int, address: int) -> int:
|
|
302
|
+
if size == 2:
|
|
303
|
+
return self._read_word(address)
|
|
304
|
+
if size == 1:
|
|
305
|
+
return self._read_byte(address)
|
|
306
|
+
return self._read_long(address)
|
|
307
|
+
|
|
308
|
+
def _write(self, size: int, address: int, value: int) -> None:
|
|
309
|
+
"""A byte or word write; every long write orders its halves at its caller."""
|
|
310
|
+
if size == 2:
|
|
311
|
+
self._write_word(address, value)
|
|
312
|
+
else:
|
|
313
|
+
self._write_byte(address, value)
|
|
314
|
+
|
|
315
|
+
def _read_program_word(self, address: int) -> int:
|
|
316
|
+
if address & 1:
|
|
317
|
+
raise self._fault(VECTOR_ADDRESS_ERROR, address, False, True)
|
|
318
|
+
self._cycles += 4
|
|
319
|
+
try:
|
|
320
|
+
return self._read_program(address & MASK24)
|
|
321
|
+
except BusError:
|
|
322
|
+
raise self._fault(VECTOR_BUS_ERROR, address, False, True) from None
|
|
323
|
+
|
|
324
|
+
def _read_program_operand(self, size: int, address: int) -> int:
|
|
325
|
+
"""A PC-relative operand: read in program space (UM Table 3-2, FC 2/6)."""
|
|
326
|
+
if size == 1:
|
|
327
|
+
self._cycles += 4
|
|
328
|
+
try:
|
|
329
|
+
return self._read_program_byte(address & MASK24)
|
|
330
|
+
except BusError:
|
|
331
|
+
raise self._fault(VECTOR_BUS_ERROR, address, False, True) from None
|
|
332
|
+
high = self._read_program_word(address)
|
|
333
|
+
if size == 2:
|
|
334
|
+
return high
|
|
335
|
+
return (high << 16) | self._read_program_word(address + 2)
|
|
336
|
+
|
|
337
|
+
# -- the prefetch queue ---------------------------------------------------
|
|
338
|
+
|
|
339
|
+
def _commit_pc(self) -> None:
|
|
340
|
+
"""Copy the fetch address into the microcode's PC register.
|
|
341
|
+
|
|
342
|
+
Only an address error can see the difference: it stacks this value
|
|
343
|
+
(UM 6.2.5 "unpredictable"; the steps that commit it are the
|
|
344
|
+
corpus's, T3).
|
|
345
|
+
"""
|
|
346
|
+
self._fault_pc = self._pc
|
|
347
|
+
|
|
348
|
+
# The three refills below write _read_program_word's body out instead of
|
|
349
|
+
# calling it: they run once or more per instruction, and a Python frame
|
|
350
|
+
# is the cost that dominates this core (rung C, docs/validation.md "Speed").
|
|
351
|
+
# They make no odd-address check because _pc is always even: every path
|
|
352
|
+
# that sets it either faults first on an odd target (_jump, _jump_idle,
|
|
353
|
+
# DBcc, JSR read the target's word through _read_program_word before
|
|
354
|
+
# taking it) or refuses one (set_pc, CPUState), and the refills add 2.
|
|
355
|
+
|
|
356
|
+
def _extension(self) -> int:
|
|
357
|
+
"""Take the extension word waiting in IRC and refill IRC from _pc."""
|
|
358
|
+
value = self.irc
|
|
359
|
+
address = self._pc
|
|
360
|
+
self._cycles += 4
|
|
361
|
+
try:
|
|
362
|
+
self.irc = self._read_program(address & MASK24)
|
|
363
|
+
except BusError:
|
|
364
|
+
raise self._fault(VECTOR_BUS_ERROR, address, False, True) from None
|
|
365
|
+
self._pc = (address + 2) & 0xFFFFFFFF
|
|
366
|
+
return value
|
|
367
|
+
|
|
368
|
+
def _extension_long(self) -> int:
|
|
369
|
+
high = self._extension()
|
|
370
|
+
return (high << 16) | self._extension()
|
|
371
|
+
|
|
372
|
+
def _prefetch(self) -> None:
|
|
373
|
+
"""The last step of every instruction: IRC moves to IR, IRC refills.
|
|
374
|
+
|
|
375
|
+
The opcode of the next instruction is then in IR and its first
|
|
376
|
+
extension word in IRC (UM "Prefetch"; corpus ``prefetch`` pair).
|
|
377
|
+
"""
|
|
378
|
+
address = self._fault_pc = self._pc
|
|
379
|
+
# IR takes IRC, and the decoder (IRD) takes IR, before the read: a
|
|
380
|
+
# bus error on this read already stacks the next opcode (every
|
|
381
|
+
# faulting closing prefetch of the corpus, T3, where the fault is an
|
|
382
|
+
# odd jump target's caught in _jump before the queue moves).
|
|
383
|
+
self.ir = self._opcode = self.irc
|
|
384
|
+
self._cycles += 4
|
|
385
|
+
try:
|
|
386
|
+
self.irc = self._read_program(address & MASK24)
|
|
387
|
+
except BusError:
|
|
388
|
+
raise self._fault(VECTOR_BUS_ERROR, address, False, True) from None
|
|
389
|
+
self._pc = (address + 2) & 0xFFFFFFFF
|
|
390
|
+
|
|
391
|
+
def _prefetch_before_write(self) -> None:
|
|
392
|
+
"""The closing prefetch of an instruction that still has a write to do.
|
|
393
|
+
|
|
394
|
+
IR takes IRC and IRC refills, but the decoder keeps the current
|
|
395
|
+
opcode until the handler hands it over with ``self._opcode =
|
|
396
|
+
self.ir`` at the step the microcode does (SST MOVE.l, PEA; T3): an
|
|
397
|
+
address error on
|
|
398
|
+
a write before that point stacks the old opcode.
|
|
399
|
+
"""
|
|
400
|
+
address = self._fault_pc = self._pc
|
|
401
|
+
self.ir = self.irc
|
|
402
|
+
self._cycles += 4
|
|
403
|
+
try:
|
|
404
|
+
self.irc = self._read_program(address & MASK24)
|
|
405
|
+
except BusError:
|
|
406
|
+
raise self._fault(VECTOR_BUS_ERROR, address, False, True) from None
|
|
407
|
+
self._pc = (address + 2) & 0xFFFFFFFF
|
|
408
|
+
|
|
409
|
+
def _jump(self, target: int) -> None:
|
|
410
|
+
"""Refill the queue from ``target``: two program reads (UM Table 8-9).
|
|
411
|
+
|
|
412
|
+
The first read lands in IRC; the closing prefetch moves it to IR and
|
|
413
|
+
reads the word after it.
|
|
414
|
+
"""
|
|
415
|
+
target &= 0xFFFFFFFF
|
|
416
|
+
self.irc = self._read_program_word(target)
|
|
417
|
+
self._pc = (target + 2) & 0xFFFFFFFF
|
|
418
|
+
self._prefetch()
|
|
419
|
+
|
|
420
|
+
def _jump_idle(self, target: int) -> None:
|
|
421
|
+
"""A jump whose refill has two internal clocks between its reads."""
|
|
422
|
+
target &= 0xFFFFFFFF
|
|
423
|
+
self.irc = self._read_program_word(target)
|
|
424
|
+
self._pc = (target + 2) & 0xFFFFFFFF
|
|
425
|
+
self._cycles += 2
|
|
426
|
+
self._prefetch()
|
|
427
|
+
|
|
428
|
+
# -- the stack -------------------------------------------------------------
|
|
429
|
+
|
|
430
|
+
def _push_long(self, value: int) -> None:
|
|
431
|
+
"""Push a long, high word first (BSR, JSR, PEA; corpus, T3)."""
|
|
432
|
+
sp = (self.R[15] - 4) & 0xFFFFFFFF
|
|
433
|
+
self.R[15] = sp
|
|
434
|
+
self._write_long(sp, value)
|
|
435
|
+
|
|
436
|
+
def _pop_word(self) -> int:
|
|
437
|
+
sp = self.R[15]
|
|
438
|
+
value = self._read_word(sp)
|
|
439
|
+
self.R[15] = (sp + 2) & 0xFFFFFFFF
|
|
440
|
+
return value
|
|
441
|
+
|
|
442
|
+
def _pop_long(self) -> int:
|
|
443
|
+
sp = self.R[15]
|
|
444
|
+
value = self._read_long(sp)
|
|
445
|
+
self.R[15] = (sp + 4) & 0xFFFFFFFF
|
|
446
|
+
return value
|
|
447
|
+
|
|
448
|
+
# -- exceptions (UM 6.2, 6.3) ---------------------------------------------
|
|
449
|
+
|
|
450
|
+
def _enter_supervisor(self) -> int:
|
|
451
|
+
"""Step 1 of exception processing: copy SR, set S, clear T (UM 6.2.5)."""
|
|
452
|
+
saved = self.SR
|
|
453
|
+
self._set_sr((saved | S) & ~T)
|
|
454
|
+
return saved
|
|
455
|
+
|
|
456
|
+
def _exception(
|
|
457
|
+
self,
|
|
458
|
+
vector: int,
|
|
459
|
+
pc: int,
|
|
460
|
+
*,
|
|
461
|
+
idle: int = 4,
|
|
462
|
+
saved: int | None = None,
|
|
463
|
+
by_instruction: bool = False,
|
|
464
|
+
) -> None:
|
|
465
|
+
"""Group 1 and 2 exception entry with the three-word frame (UM Figure 6-5).
|
|
466
|
+
|
|
467
|
+
``pc`` is the value to stack; ``idle`` the internal clocks before the
|
|
468
|
+
first push. The pushes, the vector fetch and the refill are in the
|
|
469
|
+
order the corpus records: PC low, SR, PC high, vector high and low,
|
|
470
|
+
then the handler's first two words with two idle clocks between.
|
|
471
|
+
``by_instruction`` marks group 2 (TRAP, TRAPV, CHK, divide by zero),
|
|
472
|
+
whose processing the instruction itself starts.
|
|
473
|
+
"""
|
|
474
|
+
entered = self._enter_supervisor()
|
|
475
|
+
if saved is None:
|
|
476
|
+
saved = entered
|
|
477
|
+
self._trace_pending = False
|
|
478
|
+
self._cycles += idle
|
|
479
|
+
# An address or bus error from here on is one "not an instruction"
|
|
480
|
+
# access, I/N set in its information word (UM Figure 6-7), except in
|
|
481
|
+
# group 2, where the instruction started the processing: I/N clear
|
|
482
|
+
# (WinUAE's tester core, T2 by running; MAME 0.285's microcode, T3).
|
|
483
|
+
self._processing_exception = not by_instruction
|
|
484
|
+
sp = (self.R[15] - 6) & 0xFFFFFFFF
|
|
485
|
+
self.R[15] = sp
|
|
486
|
+
self._write_word(sp + 4, pc)
|
|
487
|
+
self._write_word(sp, saved)
|
|
488
|
+
self._write_word(sp + 2, pc >> 16)
|
|
489
|
+
self._fault_pc = pc
|
|
490
|
+
target = self._read_vector(vector)
|
|
491
|
+
self._jump_idle(target)
|
|
492
|
+
self._processing_exception = False
|
|
493
|
+
|
|
494
|
+
def _read_vector(self, vector: int) -> int:
|
|
495
|
+
address = vector << 2
|
|
496
|
+
high = self._read_word(address)
|
|
497
|
+
return (high << 16) | self._read_word(address + 2)
|
|
498
|
+
|
|
499
|
+
def _group_zero(self, fault: GroupZero) -> None:
|
|
500
|
+
"""Address or bus error: abort, stack the seven-word frame (UM Figure 6-7).
|
|
501
|
+
|
|
502
|
+
The aborted access costs its four clocks and four more; two internal
|
|
503
|
+
steps of two clocks follow (the corpus's 55,606 address errors, T3;
|
|
504
|
+
WinUAE run agrees on the clocks). The frame is written in
|
|
505
|
+
the corpus's order: PC low, SR, PC high, IR, access address low,
|
|
506
|
+
access information, access address high. A second group 0 fault
|
|
507
|
+
while doing this halts the processor (UM 5.4.4).
|
|
508
|
+
"""
|
|
509
|
+
if fault.vector == VECTOR_ADDRESS_ERROR and self._address_error_hook is not None:
|
|
510
|
+
self._address_error_hook(fault.address & MASK24, fault.write, self._fc(fault.program))
|
|
511
|
+
self._cycles += 4 + 4 + 2 + 2
|
|
512
|
+
# The access information word: bits 15-5 are the undefined part and
|
|
513
|
+
# carry IR's (the corpus's faulting cases, T3; WinUAE run agrees on
|
|
514
|
+
# every frame it judges; UM Figure 6-7 marks them undefined), R/W is
|
|
515
|
+
# bit 4, I/N bit 3 (set when the access was part of exception
|
|
516
|
+
# processing rather than of an instruction), and the function code.
|
|
517
|
+
information = (
|
|
518
|
+
(self._opcode & 0xFFE0)
|
|
519
|
+
| (0 if fault.write else 0x10)
|
|
520
|
+
| (0x08 if self._processing_exception else 0)
|
|
521
|
+
| self._fc(fault.program)
|
|
522
|
+
)
|
|
523
|
+
self._processing_exception = False
|
|
524
|
+
saved = self._enter_supervisor()
|
|
525
|
+
self._trace_pending = False
|
|
526
|
+
pc = self._fault_pc
|
|
527
|
+
sp = (self.R[15] - 14) & 0xFFFFFFFF
|
|
528
|
+
self.R[15] = sp
|
|
529
|
+
try:
|
|
530
|
+
self._write_word(sp + 12, pc)
|
|
531
|
+
self._write_word(sp + 8, saved)
|
|
532
|
+
self._write_word(sp + 10, pc >> 16)
|
|
533
|
+
self._write_word(sp + 6, self._opcode)
|
|
534
|
+
self._write_word(sp + 4, fault.address)
|
|
535
|
+
self._write_word(sp, information)
|
|
536
|
+
self._write_word(sp + 2, fault.address >> 16)
|
|
537
|
+
target = self._read_vector(fault.vector)
|
|
538
|
+
self._jump_idle(target)
|
|
539
|
+
except GroupZero:
|
|
540
|
+
# A group 0 fault while processing a group 0 exception: the double
|
|
541
|
+
# bus fault halts the processor until reset (UM 5.4.4, 6.3.9.1).
|
|
542
|
+
# MAME 0.285 takes another address error instead; the corpus has
|
|
543
|
+
# no such case, and the manual is followed (docs/claims.md,
|
|
544
|
+
# "Undecidable here").
|
|
545
|
+
self.halted = True
|