summaryrefslogtreecommitdiff
path: root/lib/nes/sys/cpu.hh
blob: d0901c0bdfde7352a126db04bf81ffbd71d4a2f5 (plain)
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
#pragma once

#include "nes/sys/types/cycle-count.hh"
#include "nes/sys/types/address.hh"
#include "nes/sys/types/status.hh"

namespace nes
{
	class ppu;
	class controller;
	class cartridge;
	struct snapshot;

	class cpu
	{
		static constexpr auto ram_size = std::size_t{ 0x800 };
		static constexpr auto stack_offset = address{ 0x100 };

		cycle_count current_cycles_;
		ppu& ppu_;
		cartridge& cartridge_;
		controller& controller_1_;
		controller& controller_2_;
		std::uint8_t ram_[ram_size]{};
		bool nmi_pending_{ false };

		// Registers
		struct
		{
			std::uint16_t pc{ 0 };
			std::uint8_t sp{ 0xFD };
			std::uint8_t a{ 0 };
			std::uint8_t x{ 0 };
			std::uint8_t y{ 0 };
			struct
			{
				auto get_c() const -> bool { return value & 0b00000001; } // carry
				auto get_z() const -> bool { return value & 0b00000010; } // zero
				auto get_i() const -> bool { return value & 0b00000100; } // interrupt inhibit
				auto get_d() const -> bool { return value & 0b00001000; } // decimal
				auto get_b() const -> bool { return value & 0b00010000; } // break
				auto get_v() const -> bool { return value & 0b01000000; } // overflow
				auto get_n() const -> bool { return value & 0b10000000; } // negative

				auto set_c(bool const v) -> void { value = (value & ~0b00000001) | (v ? 0b00000001 : 0); }
				auto set_z(bool const v) -> void { value = (value & ~0b00000010) | (v ? 0b00000010 : 0); }
				auto set_i(bool const v) -> void { value = (value & ~0b00000100) | (v ? 0b00000100 : 0); }
				auto set_d(bool const v) -> void { value = (value & ~0b00001000) | (v ? 0b00001000 : 0); }
				auto set_b(bool const v) -> void { value = (value & ~0b00010000) | (v ? 0b00010000 : 0); }
				auto set_v(bool const v) -> void { value = (value & ~0b01000000) | (v ? 0b01000000 : 0); }
				auto set_n(bool const v) -> void { value = (value & ~0b10000000) | (v ? 0b10000000 : 0); }

				std::uint8_t value{ 0b00100100 };
			} p{};
		} registers_{};

		enum class addressing_mode
		{
			accumulator,          // A
			immediate,            // #v
			zero_page,            // d
			absolute,             // a
			relative,             // *+d
			indirect,             // (a)
			zero_page_indexed_x,  // d,x
			zero_page_indexed_y,  // d,y
			absolute_indexed_x,   // a,x
			absolute_indexed_y,   // a,y
			indexed_indirect,     // (d,x)
			indirect_indexed,     // (d),y
		};

		enum class force_page_crossing
		{
			no,
			yes,
		};

		template<addressing_mode>
		class operand
		{
			cpu& cpu_;
			address address_;
			cycle_count cycles_;

		public:
			explicit operand(cpu& cpu, address const address, cycle_count const cycles)
				: cpu_{ cpu }
				, address_{ address }
				, cycles_{ cycles }
			{
			}

			auto read() -> std::uint8_t { return cpu_.read8(address_); }
			auto write(std::uint8_t const value) -> void { cpu_.write8(address_, value); }
			auto get_address() -> address { return address_; }
			auto get_cycles() -> cycle_count { return cycles_; }
		};

		template<>
		class operand<addressing_mode::accumulator>
		{
			cpu& cpu_;

		public:
			explicit operand(cpu& cpu)
				: cpu_{ cpu }
			{
			}

			auto read() -> std::uint8_t { return cpu_.registers_.a; }
			auto write(std::uint8_t const value) -> void { cpu_.registers_.a = value; }
			auto get_cycles() -> cycle_count { return cycle_count::from_cpu(2); }
		};

		template<>
		class operand<addressing_mode::immediate>
		{
			std::uint8_t value_{ 0 };

		public:
			explicit operand(std::uint8_t const value)
				: value_{ value }
			{
			}

			auto read() -> std::uint8_t { return value_; }
			auto get_cycles() -> cycle_count { return cycle_count::from_cpu(2); }
		};

	public:
		explicit cpu(ppu&, cartridge&, controller& controller_1, controller& controller_2);

		cpu(cpu const&) = delete;
		cpu(cpu&&) = delete;
		auto operator=(cpu const&) -> cpu& = delete;
		auto operator=(cpu&&) -> cpu& = delete;

		auto get_cycles() const -> cycle_count { return current_cycles_; }
		auto build_snapshot(snapshot&) -> void;
		auto stall_cycles(cycle_count) -> void;
		auto trigger_nmi() -> void;
		auto step() -> status;
		auto is_nmi_pending() const -> bool { return nmi_pending_; } // XXX: Debugging

		// Memory access

		auto read8(address) -> std::uint8_t;
		auto read16(address) -> std::uint16_t;
		auto write8(address, std::uint8_t) -> void;
		auto write16(address, std::uint16_t) -> void;

	private:
		// Instructions

#define DEFINE_SIMPLE_INSTRUCTION(name) \
	auto run_##name() -> status;
#define DEFINE_OPERAND_INSTRUCTION(name) \
	template<addressing_mode Mode> auto run_##name() -> status;

		DEFINE_OPERAND_INSTRUCTION(brk)
		DEFINE_OPERAND_INSTRUCTION(ora)
		DEFINE_SIMPLE_INSTRUCTION(stp)
		DEFINE_OPERAND_INSTRUCTION(slo)
		DEFINE_OPERAND_INSTRUCTION(nop)
		DEFINE_SIMPLE_INSTRUCTION(nop)
		DEFINE_SIMPLE_INSTRUCTION(php)
		DEFINE_OPERAND_INSTRUCTION(asl)
		DEFINE_OPERAND_INSTRUCTION(anc)
		DEFINE_OPERAND_INSTRUCTION(bpl)
		DEFINE_SIMPLE_INSTRUCTION(clc)
		DEFINE_OPERAND_INSTRUCTION(jsr)
		DEFINE_OPERAND_INSTRUCTION(and)
		DEFINE_OPERAND_INSTRUCTION(rla)
		DEFINE_OPERAND_INSTRUCTION(bit)
		DEFINE_OPERAND_INSTRUCTION(rol)
		DEFINE_SIMPLE_INSTRUCTION(plp)
		DEFINE_OPERAND_INSTRUCTION(bmi)
		DEFINE_SIMPLE_INSTRUCTION(sec)
		DEFINE_SIMPLE_INSTRUCTION(rti)
		DEFINE_OPERAND_INSTRUCTION(eor)
		DEFINE_OPERAND_INSTRUCTION(sre)
		DEFINE_OPERAND_INSTRUCTION(lsr)
		DEFINE_OPERAND_INSTRUCTION(alr)
		DEFINE_SIMPLE_INSTRUCTION(pha)
		DEFINE_OPERAND_INSTRUCTION(jmp)
		DEFINE_OPERAND_INSTRUCTION(bvc)
		DEFINE_SIMPLE_INSTRUCTION(cli)
		DEFINE_SIMPLE_INSTRUCTION(rts)
		DEFINE_OPERAND_INSTRUCTION(adc)
		DEFINE_OPERAND_INSTRUCTION(rra)
		DEFINE_OPERAND_INSTRUCTION(ror)
		DEFINE_OPERAND_INSTRUCTION(arr)
		DEFINE_SIMPLE_INSTRUCTION(pla)
		DEFINE_OPERAND_INSTRUCTION(bvs)
		DEFINE_SIMPLE_INSTRUCTION(sei)
		DEFINE_OPERAND_INSTRUCTION(sta)
		DEFINE_OPERAND_INSTRUCTION(ahx)
		DEFINE_OPERAND_INSTRUCTION(sax)
		DEFINE_OPERAND_INSTRUCTION(sty)
		DEFINE_OPERAND_INSTRUCTION(stx)
		DEFINE_SIMPLE_INSTRUCTION(dey)
		DEFINE_SIMPLE_INSTRUCTION(txa)
		DEFINE_OPERAND_INSTRUCTION(xaa)
		DEFINE_OPERAND_INSTRUCTION(bcc)
		DEFINE_SIMPLE_INSTRUCTION(tya)
		DEFINE_SIMPLE_INSTRUCTION(txs)
		DEFINE_OPERAND_INSTRUCTION(tas)
		DEFINE_OPERAND_INSTRUCTION(shy)
		DEFINE_OPERAND_INSTRUCTION(shx)
		DEFINE_OPERAND_INSTRUCTION(ldy)
		DEFINE_OPERAND_INSTRUCTION(lda)
		DEFINE_OPERAND_INSTRUCTION(ldx)
		DEFINE_OPERAND_INSTRUCTION(lax)
		DEFINE_SIMPLE_INSTRUCTION(tay)
		DEFINE_SIMPLE_INSTRUCTION(tax)
		DEFINE_OPERAND_INSTRUCTION(bcs)
		DEFINE_SIMPLE_INSTRUCTION(clv)
		DEFINE_SIMPLE_INSTRUCTION(tsx)
		DEFINE_OPERAND_INSTRUCTION(las)
		DEFINE_OPERAND_INSTRUCTION(cpy)
		DEFINE_OPERAND_INSTRUCTION(axs)
		DEFINE_OPERAND_INSTRUCTION(cmp)
		DEFINE_OPERAND_INSTRUCTION(dcp)
		DEFINE_OPERAND_INSTRUCTION(dec)
		DEFINE_SIMPLE_INSTRUCTION(iny)
		DEFINE_SIMPLE_INSTRUCTION(dex)
		DEFINE_OPERAND_INSTRUCTION(bne)
		DEFINE_SIMPLE_INSTRUCTION(cld)
		DEFINE_OPERAND_INSTRUCTION(cpx)
		DEFINE_OPERAND_INSTRUCTION(sbc)
		DEFINE_OPERAND_INSTRUCTION(isc)
		DEFINE_OPERAND_INSTRUCTION(inc)
		DEFINE_SIMPLE_INSTRUCTION(inx)
		DEFINE_OPERAND_INSTRUCTION(beq)
		DEFINE_SIMPLE_INSTRUCTION(sed)

#undef DEFINE_SIMPLE_INSTRUCTION
#undef DEFINE_OPERAND_INSTRUCTION

		// Helpers

		auto advance_pc8() -> std::uint8_t;
		auto advance_pc16() -> std::uint16_t;
		auto push_stack8(std::uint8_t) -> void;
		auto push_stack16(std::uint16_t) -> void;
		auto pop_stack8() -> std::uint8_t;
		auto pop_stack16() -> std::uint16_t;
		auto update_zn(std::uint8_t value) -> void;
		template<addressing_mode Mode>
		auto branch(bool condition) -> void;
		auto execute_interrupt(address) -> void;

		auto eval_ror(std::uint8_t arg) -> std::uint8_t;
		auto eval_rol(std::uint8_t arg) -> std::uint8_t;
		auto eval_asl(std::uint8_t arg) -> std::uint8_t;
		auto eval_lsr(std::uint8_t arg) -> std::uint8_t;
		auto eval_adc(std::uint8_t arg) -> void;
		auto eval_and(std::uint8_t arg) -> void;
		auto eval_ora(std::uint8_t arg) -> void;
		auto eval_eor(std::uint8_t arg) -> void;
		auto eval_cmp(std::uint8_t a, std::uint8_t b) -> void;
		auto eval_plp() -> void;
		auto eval_php() -> void;

		// Operands

#define DEFINE_CYCLE_COUNT(name) \
	template<addressing_mode Mode> static auto name##_cycle_count() -> cycle_count;
#define DEFINE_CYCLE_COUNT_INSTANCE(name, mode, value) \
	template<> auto name##_cycle_count<addressing_mode::mode>() -> cycle_count { return cycle_count::from_cpu(value); }

		DEFINE_CYCLE_COUNT(jump)
		DEFINE_CYCLE_COUNT_INSTANCE(jump, absolute, 3)
		DEFINE_CYCLE_COUNT_INSTANCE(jump, indirect, 5)

		DEFINE_CYCLE_COUNT(shift)
		DEFINE_CYCLE_COUNT_INSTANCE(shift, accumulator, 2)
		DEFINE_CYCLE_COUNT_INSTANCE(shift, zero_page, 5)
		DEFINE_CYCLE_COUNT_INSTANCE(shift, zero_page_indexed_x, 6)
		DEFINE_CYCLE_COUNT_INSTANCE(shift, absolute, 6)
		DEFINE_CYCLE_COUNT_INSTANCE(shift, absolute_indexed_x, 7)

		DEFINE_CYCLE_COUNT(inc_dec)
		DEFINE_CYCLE_COUNT_INSTANCE(inc_dec, zero_page, 5)
		DEFINE_CYCLE_COUNT_INSTANCE(inc_dec, zero_page_indexed_x, 6)
		DEFINE_CYCLE_COUNT_INSTANCE(inc_dec, absolute, 6)
		DEFINE_CYCLE_COUNT_INSTANCE(inc_dec, absolute_indexed_x, 7)

#undef DEFINE_CYCLE_COUNT
#undef DEFINE_CYCLE_COUNT_INSTANCE

		template<addressing_mode Mode>
		auto fetch_operand(force_page_crossing = force_page_crossing::no) -> operand<Mode>;
		template<>
		auto fetch_operand<addressing_mode::accumulator>(force_page_crossing) -> operand<addressing_mode::accumulator>;
		template<>
		auto fetch_operand<addressing_mode::immediate>(force_page_crossing) -> operand<addressing_mode::immediate>;
	};
} // namespace nes