1 /*
2 * This file is part of libsidplayfp, a SID player engine.
3 *
4 * Copyright (C) 2020 Leandro Nini
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include "UnitTest++/UnitTest++.h"
22 #include "UnitTest++/TestReporter.h"
23
24
25 #include "../src/EventScheduler.h"
26 #include "../src/EventScheduler.cpp"
27
28 #define private protected
29
30 #include "../src/c64/CPU/mos6510.h"
31 #include "../src/c64/CPU/opcodes.h"
32 #include "../src/c64/CPU/mos6510.cpp"
33
34 #include <iostream>
35 #include <iomanip>
36
37 using namespace UnitTest;
38 using namespace libsidplayfp;
39
40 class testcpu final : public MOS6510
41 {
42 private:
43 uint8_t mem[65536];
44
45 private:
getInstr() const46 uint8_t getInstr() const { return cycleCount >> 3; }
47
48 protected:
cpuRead(uint_least16_t addr)49 uint8_t cpuRead(uint_least16_t addr) override { return mem[addr]; }
50
cpuWrite(uint_least16_t addr,uint8_t data)51 void cpuWrite(uint_least16_t addr, uint8_t data) override { mem[addr] = data; }
52
53 public:
testcpu(EventScheduler & scheduler)54 testcpu(EventScheduler &scheduler) :
55 MOS6510(scheduler)
56 {
57 mem[0xFFFC] = 0x00;
58 mem[0xFFFD] = 0x10;
59 }
60
setMem(uint8_t offset,uint8_t opcode)61 void setMem(uint8_t offset, uint8_t opcode) { mem[0x1000+offset] = opcode; }
62
print()63 void print() {
64 std::cout << "-> " << std::hex << (int)getInstr() << std::endl;
65 }
66
check(uint8_t opcode) const67 bool check(uint8_t opcode) const { return getInstr() == opcode; }
68 };
69
SUITE(mos6510)70 SUITE(mos6510)
71 {
72
73 struct TestFixture
74 {
75 // Test setup
76 TestFixture() :
77 cpu(scheduler)
78 {
79 scheduler.reset();
80 cpu.reset();
81 }
82
83 EventScheduler scheduler;
84 testcpu cpu;
85 };
86
87 /*
88 * Interrupt is recognized at T0 and triggered on the following T1
89 *
90 * http://visual6502.org/JSSim/expert.html?graphics=f&loglevel=2&steps=20&a=0010&d=58eaeaeaeaea&a=fffe&d=2000&a=0020&d=e840&r=0010&irq0=4&irq1=100&logmore=rdy,irq
91 */
92 TEST_FIXTURE(TestFixture, TestNop)
93 {
94 cpu.setMem(0, CLIn);
95 cpu.setMem(1, NOPn);
96
97 scheduler.clock(); // T1
98 scheduler.clock(); // T0+T2
99
100 cpu.triggerIRQ();
101 scheduler.clock(); // T1
102 scheduler.clock(); // T0+T2
103 scheduler.clock(); // T1
104 CHECK(cpu.check(BRKn));
105 }
106
107 /*
108 * Interrupt is not recognized at T0 as the I flag is still set
109 * It is recognized during the following opcode's T0
110 *
111 * http://visual6502.org/JSSim/expert.html?graphics=f&loglevel=2&steps=20&a=0010&d=7858eaeaeaea&a=fffe&d=2000&a=0020&d=e840&r=0010&irq0=4&irq1=100&logmore=rdy,irq
112 */
113 TEST_FIXTURE(TestFixture, TestCli)
114 {
115 cpu.setMem(0, SEIn);
116 cpu.setMem(1, CLIn);
117 cpu.setMem(2, NOPn);
118
119 scheduler.clock(); // T1
120 scheduler.clock(); // T0+T2
121
122 cpu.triggerIRQ();
123 scheduler.clock(); // T1
124 scheduler.clock(); // T0+T2
125 scheduler.clock(); // T1
126 CHECK(cpu.check(NOPn));
127
128 scheduler.clock(); // T0+T2
129 scheduler.clock(); // T1
130 CHECK(cpu.check(BRKn));
131 }
132
133 /*
134 * Interrupt is recognized at T0 during CLI
135 * as the I flag is cleared while the CPU is stalled by RDY line
136 * It is triggered on the following T1
137 *
138 * http://visual6502.org/JSSim/expert.html?graphics=f&loglevel=2&steps=20&a=0010&d=7858eaeaeaea&a=fffe&d=2000&a=0020&d=e840&r=0010&irq0=4&irq1=100&logmore=rdy,irq&rdy0=6&rdy1=8
139 */
140 TEST_FIXTURE(TestFixture, TestCliRdy)
141 {
142 cpu.setMem(0, SEIn);
143 cpu.setMem(1, CLIn);
144 cpu.setMem(2, NOPn);
145
146 scheduler.clock(); // T1
147 scheduler.clock(); // T0+T2
148
149 cpu.triggerIRQ();
150 scheduler.clock(); // T1
151 cpu.setRDY(false);
152 scheduler.clock(); // CPU stalled but the I flag is being cleared
153 cpu.setRDY(true);
154 scheduler.clock(); // T0+T2
155 scheduler.clock(); // T1
156 CHECK(cpu.check(BRKn));
157 }
158
159 /*
160 * Interrupt is recognized at T0 as the I flag is still cleared
161 * It is triggered on the following T1
162 *
163 * http://visual6502.org/JSSim/expert.html?graphics=f&loglevel=2&steps=20&a=0010&d=5878eaeaeaea&a=fffe&d=2000&a=0020&d=e840&r=0010&irq0=4&irq1=100&logmore=rdy,irq
164 */
165 TEST_FIXTURE(TestFixture, TestSei)
166 {
167 cpu.setMem(0, CLIn);
168 cpu.setMem(1, SEIn);
169 cpu.setMem(2, NOPn);
170
171 scheduler.clock(); // T1
172 scheduler.clock(); // T0+T2
173
174 cpu.triggerIRQ();
175 scheduler.clock(); // T1
176 scheduler.clock(); // T0+T2
177 scheduler.clock(); // T1
178 CHECK(cpu.check(BRKn));
179 }
180
181 /*
182 * Interrupt is recognized at T0 during SEI
183 * even if the I flag is set while the CPU is stalled by RDY line
184 *
185 * http://visual6502.org/JSSim/expert.html?graphics=f&loglevel=2&steps=20&a=0010&d=5878eaeaeaea&a=fffe&d=2000&a=0020&d=e840&r=0010&irq0=4&irq1=100&logmore=rdy,irq&rdy0=6&rdy1=8
186 */
187 TEST_FIXTURE(TestFixture, TestSeiRdy)
188 {
189 cpu.setMem(0, CLIn);
190 cpu.setMem(1, SEIn);
191 cpu.setMem(2, NOPn);
192
193 scheduler.clock(); // T1
194 scheduler.clock(); // T0+T2
195
196 cpu.triggerIRQ();
197 scheduler.clock(); // T1
198 cpu.setRDY(false);
199 scheduler.clock(); // CPU stalled but the I flag is being set
200 cpu.setRDY(true);
201 scheduler.clock(); // T0+T2
202 scheduler.clock(); // T1
203 CHECK(cpu.check(BRKn));
204 }
205
206 /*
207 * Interrupt is not recognized at T0 during SEI
208 * even if the I flag is set while the CPU is stalled by RDY line
209 *
210 * http://visual6502.org/JSSim/expert.html?graphics=f&loglevel=2&steps=20&a=0010&d=5878eaeaeaea&a=fffe&d=2000&a=0020&d=e840&r=0010&irq0=6&irq1=100&logmore=rdy,irq&rdy0=6&rdy1=8
211 */
212 TEST_FIXTURE(TestFixture, TestSeiRdy2)
213 {
214 cpu.setMem(0, CLIn);
215 cpu.setMem(1, SEIn);
216 cpu.setMem(2, NOPn);
217
218 scheduler.clock(); // T1
219 scheduler.clock(); // T0+T2
220
221 scheduler.clock(); // T1
222 cpu.triggerIRQ();
223 cpu.setRDY(false);
224 scheduler.clock(); // CPU stalled but the I flag is being set
225 cpu.setRDY(true);
226 scheduler.clock(); // T0+T2
227 scheduler.clock(); // T1
228 CHECK(cpu.check(NOPn));
229 }
230
231 /*
232 * Interrupt is not recognized at T0 as the I flag is still set
233 * It is recognized during the following opcode's T0
234 *
235 * http://visual6502.org/JSSim/expert.html?graphics=f&loglevel=2&steps=30&a=0010&d=58087828eaeaeaea&a=fffe&d=2000&a=0020&d=e840&r=0010&irq0=14&irq1=100&logmore=rdy,irq
236 */
237 TEST_FIXTURE(TestFixture, TestPlp1)
238 {
239 cpu.setMem(0, CLIn);
240 cpu.setMem(1, PHPn);
241 cpu.setMem(2, SEIn);
242 cpu.setMem(3, PLPn);
243 cpu.setMem(4, NOPn);
244
245 scheduler.clock(); // T1
246 scheduler.clock(); // T0+T2
247
248 scheduler.clock(); // T1
249 scheduler.clock(); // T2
250 scheduler.clock(); // T0
251
252 scheduler.clock(); // T1
253 scheduler.clock(); // T0+T2
254
255 cpu.triggerIRQ();
256 scheduler.clock(); // T1
257 scheduler.clock(); // T2
258 scheduler.clock(); // T3
259 scheduler.clock(); // T0
260 scheduler.clock(); // T1
261 CHECK(cpu.check(NOPn));
262
263 scheduler.clock(); // T0+T2
264 scheduler.clock(); // T1
265 CHECK(cpu.check(BRKn));
266 }
267
268 /*
269 * Interrupt is not recognized at T0 as the I flag is still set
270 * It is recognized during the following opcode's T0
271 *
272 * http://visual6502.org/JSSim/expert.html?graphics=f&loglevel=2&steps=30&a=0010&d=58087828eaeaeaea&a=fffe&d=2000&a=0020&d=e840&r=0010&irq0=14&irq1=100&logmore=rdy,irq&rdy0=20&rdy1=22
273 */
274 TEST_FIXTURE(TestFixture, TestPlp1Rdy)
275 {
276 cpu.setMem(0, CLIn);
277 cpu.setMem(1, PHPn);
278 cpu.setMem(2, SEIn);
279 cpu.setMem(3, PLPn);
280 cpu.setMem(4, NOPn);
281
282 scheduler.clock(); // T1
283 scheduler.clock(); // T0+T2
284
285 scheduler.clock(); // T1
286 scheduler.clock(); // T2
287 scheduler.clock(); // T0
288
289 scheduler.clock(); // T1
290 scheduler.clock(); // T0+T2
291
292 cpu.triggerIRQ();
293 scheduler.clock(); // T1
294 scheduler.clock(); // T2
295 scheduler.clock(); // T3
296 cpu.setRDY(false);
297 scheduler.clock(); // CPU stalled
298 cpu.setRDY(true);
299 scheduler.clock(); // T0
300 scheduler.clock(); // T1
301 CHECK(cpu.check(NOPn));
302
303 scheduler.clock(); // T0+T2
304 scheduler.clock(); // T1
305 CHECK(cpu.check(BRKn));
306 }
307
308 /*
309 * Interrupt is not recognized at T0 as the I flag is still set
310 * It is recognized during the following opcode's T0
311 *
312 * http://visual6502.org/JSSim/expert.html?graphics=f&loglevel=2&steps=30&a=0010&d=78085828eaeaeaea&a=fffe&d=2000&a=0020&d=e840&r=0010&irq0=14&irq1=100&logmore=rdy,irq
313 */
314 TEST_FIXTURE(TestFixture, TestPlp2)
315 {
316 cpu.setMem(0, SEIn);
317 cpu.setMem(1, PHPn);
318 cpu.setMem(2, CLIn);
319 cpu.setMem(3, PLPn);
320 cpu.setMem(4, NOPn);
321
322 scheduler.clock(); // T1
323 scheduler.clock(); // T0+T2
324
325 scheduler.clock(); // T1
326 scheduler.clock(); // T2
327 scheduler.clock(); // T0
328
329 scheduler.clock(); // T1
330 scheduler.clock(); // T0+T2
331
332 cpu.triggerIRQ();
333 scheduler.clock(); // T1
334 scheduler.clock(); // T2
335 scheduler.clock(); // T3
336 scheduler.clock(); // T0
337 scheduler.clock(); // T1
338 CHECK(cpu.check(BRKn));
339 }
340
341 /*
342 * Interrupt is not recognized at T0 as the I flag is still set
343 * It is recognized during the following opcode's T0
344 *
345 * http://visual6502.org/JSSim/expert.html?graphics=f&loglevel=2&steps=30&a=0010&d=78085828eaeaeaea&a=fffe&d=2000&a=0020&d=e840&r=0010&irq0=14&irq1=100&logmore=rdy,irq&rdy0=20&rdy1=22
346 */
347 TEST_FIXTURE(TestFixture, TestPlp2Rdy)
348 {
349 cpu.setMem(0, SEIn);
350 cpu.setMem(1, PHPn);
351 cpu.setMem(2, CLIn);
352 cpu.setMem(3, PLPn);
353 cpu.setMem(4, NOPn);
354
355 scheduler.clock(); // T1
356 scheduler.clock(); // T0+T2
357
358 scheduler.clock(); // T1
359 scheduler.clock(); // T2
360 scheduler.clock(); // T0
361
362 scheduler.clock(); // T1
363 scheduler.clock(); // T0+T2
364
365 cpu.triggerIRQ();
366 scheduler.clock(); // T1
367 scheduler.clock(); // T2
368 scheduler.clock(); // T3
369 cpu.setRDY(false);
370 scheduler.clock(); // CPU stalled
371 cpu.setRDY(true);
372 scheduler.clock(); // T0
373 scheduler.clock(); // T1
374 CHECK(cpu.check(BRKn));
375 }
376
377 }
378