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