-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcpu.py
173 lines (144 loc) · 5.55 KB
/
cpu.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import controller
import instruction
import mem
import opc
import ppu
import apu
import sys
STACK_BASE = 0x100
FLAG_C = 0x1 # carry
FLAG_Z = 0x2 # zero result
FLAG_I = 0x4 # interrupt disable
FLAG_D = 0x8 # decimal mode
FLAG_B = 0x10 # break command
FLAG_EXP = 0x20 # expansion
FLAG_V = 0x40 # overflow
FLAG_N = 0x80 # negative result
class CPU(object):
def __init__(self,
rom,
audioEnabled = True,
ppuDebug = False,
cheats = None):
"""Sets up an initial CPU state loading from the given ROM. Simulates
the reset signal."""
self.audioEnabled = audioEnabled
# see http://wiki.nesdev.com/w/index.php/CPU_power_up_state
# for some initial values
self.prgrom = rom.prgrom
self.prgromsize = len(self.prgrom)
self.chrrom = rom.chrrom
self.chrromsize = len(rom.chrrom)
if rom.mapper == 0:
self.mem = mem.Memory(self, rom.mirroring)
elif rom.mapper == 1:
self.mem = mem.MMC1(self, rom.mirroring)
else:
raise NotImplementedError("Unimplemented mapper %d" % mapper)
if cheats:
cheats.wrapMemory(self.mem)
# registers
self.reg_A = 0
self.reg_X = 0
self.reg_Y = 0
# stack pointer
self.SP = 0xFD
# flags
self.flags = FLAG_I | FLAG_B | FLAG_EXP
# program counter: initialize to 0; later set according to the
# reset signal handler
self.PC = 0
self.currentInstruction = 0
# TODO also add RST to this
self.irqPending = False
self.nmiPending = False
self.ppuCyclesUntilAction = 0
self.ppuStoredCycles = 0
self.apuCyclesUntilAction = 0
self.apuStoredCycles = 0
self.ppu = ppu.PPU(cpu = self,
mirroring = rom.mirroring,
ppu_debug = ppuDebug)
self.apu = apu.APU(self)
# Cycles for the PPU to catch up on. (When the CPU executes a
# cycle, this goes up by the cycle count. When the PPU
# executes a PPU cycle, this goes down by 3.)
self.excessCycles = 0
# Controller
self.controller = controller.Controller()
# If an instruction takes more time than its "cycles"
# property, it should set this value to the number of extra
# cycles it took.
self.instructionCycleExtra = 0
# Now that everything is set up, simulate the RST signal.
# If we ever track frames, this will affect those.
self.PC = self.mem.dereference(mem.VEC_RST)
def flag(self, mask):
return bool(self.flags & mask)
def setFlag(self, mask, val): # val should be boolean
if val:
self.flags |= mask
else:
self.flags &= (0xFF ^ mask)
def mathFlags(self, val):
self.setFlag(FLAG_Z, val == 0)
# FLAG_N is set to match bit 7 of the value
self.setFlag(FLAG_N, val & 0x80)
# do stack pushing and popping actually want to live in the CPU?
def stackPush(self, val):
# TODO make sure the stack pointer stays in a sane range. Some
# sort of consistency check? Properties that can only have
# limited values assigned to them?
self.mem.write(STACK_BASE + self.SP, val)
self.SP -= 1
def stackPop(self):
self.SP += 1
return self.mem.read(STACK_BASE + self.SP)
def printState(self):
print ("A = %02x X = %02x Y = %02x SP=%02x flags = %02x PC = %04x" %
(self.reg_A, self.reg_X, self.reg_Y, self.SP, self.flags, self.PC))
instr = opc.instrFromAddr(self.PC, self)
print instr.disassemble()
def interrupt(self, vector):
# much like the BRK opcode, but we don't set the B flag, and
# the new address is determined by the passed interrupt
# vector. In theory there's some "interrupt highjacking"
# behavior to emulate but I don't think I care.
pcHigh = self.PC >> 8
pcLow = self.PC & 0xff
self.stackPush(pcHigh)
self.stackPush(pcLow)
self.stackPush(self.flags)
self.setFlag(FLAG_I, True)
self.PC = self.mem.dereference(vector)
def cpuTick(self):
# let's pretend the clock doesn't exist for now
if self.nmiPending:
self.interrupt(mem.VEC_NMI)
self.nmiPending = False
elif self.irqPending and not self.flag(FLAG_I):
self.interrupt(mem.VEC_IRQ)
self.irqPending = False
# TODO also process RST here (if I feel like it)
self.currentInstruction = self.PC
instr = opc.instrFromAddr(self.PC, self)
self.PC = instr.nextaddr
# TODO this next line can't account for variable cycle counts
self.excessCycles += instr.cycles
instr.call(self)
self.excessCycles += self.instructionCycleExtra
self.instructionCycleExtra = 0
# self.printState()
def tick(self):
self.ppuStoredCycles += self.excessCycles * 3
self.apuStoredCycles += self.excessCycles
self.excessCycles = 0
if self.ppuStoredCycles >= self.ppuCyclesUntilAction:
self.ppuStoredCycles -= self.ppuCyclesUntilAction
# ppuTick sets ppuCyclesUntilAction
self.ppu.ppuTick(self.ppuCyclesUntilAction)
if self.apuStoredCycles >= self.apuCyclesUntilAction:
self.apuStoredCycles -= self.apuCyclesUntilAction
# apuTick sets apuCyclesUntilAction
self.apu.frameCounterTick()
self.cpuTick()