1 | /***** |
2 | * SPC7110 emulator - version 0.03 (2008-08-10) |
3 | * Copyright (c) 2008, byuu and neviksti |
4 | * |
5 | * Permission to use, copy, modify, and/or distribute this software for any |
6 | * purpose with or without fee is hereby granted, provided that the above |
7 | * copyright notice and this permission notice appear in all copies. |
8 | * |
9 | * The software is provided "as is" and the author disclaims all warranties |
10 | * with regard to this software including all implied warranties of |
11 | * merchantibility and fitness, in no event shall the author be liable for |
12 | * any special, direct, indirect, or consequential damages or any damages |
13 | * whatsoever resulting from loss of use, data or profits, whether in an |
14 | * action of contract, negligence or other tortious action, arising out of |
15 | * or in connection with the use or performance of this software. |
16 | *****/ |
17 | |
18 | |
19 | #define _SPC7110EMU_CPP_ |
20 | |
21 | #include "spc7110dec.cpp" |
22 | |
23 | const unsigned SPC7110::months[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; |
24 | |
25 | |
26 | void SPC7110::power() { |
27 | reset(); |
28 | } |
29 | |
30 | void SPC7110::reset() { |
31 | r4801 = 0x00; |
32 | r4802 = 0x00; |
33 | r4803 = 0x00; |
34 | r4804 = 0x00; |
35 | r4805 = 0x00; |
36 | r4806 = 0x00; |
37 | r4807 = 0x00; |
38 | r4808 = 0x00; |
39 | r4809 = 0x00; |
40 | r480a = 0x00; |
41 | r480b = 0x00; |
42 | r480c = 0x00; |
43 | |
44 | decomp.reset(); |
45 | |
46 | r4811 = 0x00; |
47 | r4812 = 0x00; |
48 | r4813 = 0x00; |
49 | r4814 = 0x00; |
50 | r4815 = 0x00; |
51 | r4816 = 0x00; |
52 | r4817 = 0x00; |
53 | r4818 = 0x00; |
54 | |
55 | r481x = 0x00; |
56 | r4814_latch = false; |
57 | r4815_latch = false; |
58 | |
59 | r4820 = 0x00; |
60 | r4821 = 0x00; |
61 | r4822 = 0x00; |
62 | r4823 = 0x00; |
63 | r4824 = 0x00; |
64 | r4825 = 0x00; |
65 | r4826 = 0x00; |
66 | r4827 = 0x00; |
67 | r4828 = 0x00; |
68 | r4829 = 0x00; |
69 | r482a = 0x00; |
70 | r482b = 0x00; |
71 | r482c = 0x00; |
72 | r482d = 0x00; |
73 | r482e = 0x00; |
74 | r482f = 0x00; |
75 | |
76 | r4830 = 0x00; |
77 | mmio_write(0x4831, 0); |
78 | mmio_write(0x4832, 1); |
79 | mmio_write(0x4833, 2); |
80 | r4834 = 0x00; |
81 | |
82 | r4840 = 0x00; |
83 | r4841 = 0x00; |
84 | r4842 = 0x00; |
85 | |
86 | if(cartridge_info_spc7110rtc) { |
87 | rtc_state = RTCS_Inactive; |
88 | rtc_mode = RTCM_Linear; |
89 | rtc_index = 0; |
90 | } |
91 | } |
92 | |
93 | unsigned SPC7110::datarom_addr(unsigned addr) { |
94 | unsigned size = memory_cartrom_size() > 0x500000 ? memory_cartrom_size() - 0x200000 : memory_cartrom_size() - 0x100000; |
95 | while(addr >= size) addr -= size; |
96 | return addr + 0x100000; |
97 | } |
98 | |
99 | unsigned SPC7110::data_pointer() { return r4811 + (r4812 << 8) + (r4813 << 16); } |
100 | unsigned SPC7110::data_adjust() { return r4814 + (r4815 << 8); } |
101 | unsigned SPC7110::data_increment() { return r4816 + (r4817 << 8); } |
102 | void SPC7110::set_data_pointer(unsigned addr) { r4811 = addr; r4812 = addr >> 8; r4813 = addr >> 16; } |
103 | void SPC7110::set_data_adjust(unsigned addr) { r4814 = addr; r4815 = addr >> 8; } |
104 | |
105 | void SPC7110::update_time(int offset) { |
106 | time_t rtc_time |
107 | = (memory_cartrtc_read(16) << 0) |
108 | | (memory_cartrtc_read(17) << 8) |
109 | | (memory_cartrtc_read(18) << 16) |
110 | | (memory_cartrtc_read(19) << 24); |
111 | time_t current_time = time(0) - offset; |
112 | |
113 | //sizeof(time_t) is platform-dependent; though memory::cartrtc needs to be platform-agnostic. |
114 | //yet platforms with 32-bit signed time_t will overflow every ~68 years. handle this by |
115 | //accounting for overflow at the cost of 1-bit precision (to catch underflow). this will allow |
116 | //memory::cartrtc timestamp to remain valid for up to ~34 years from the last update, even if |
117 | //time_t overflows. calculation should be valid regardless of number representation, time_t size, |
118 | //or whether time_t is signed or unsigned. |
119 | time_t diff |
120 | = (current_time >= rtc_time) |
121 | ? (current_time - rtc_time) |
122 | : (std::numeric_limits<time_t>::max() - rtc_time + current_time + 1); //compensate for overflow |
123 | if(diff > std::numeric_limits<time_t>::max() / 2) diff = 0; //compensate for underflow |
124 | |
125 | bool update = true; |
126 | if(memory_cartrtc_read(13) & 1) update = false; //do not update if CR0 timer disable flag is set |
127 | if(memory_cartrtc_read(15) & 3) update = false; //do not update if CR2 timer disable flags are set |
128 | |
129 | if(diff > 0 && update == true) { |
130 | unsigned second = memory_cartrtc_read( 0) + memory_cartrtc_read( 1) * 10; |
131 | unsigned minute = memory_cartrtc_read( 2) + memory_cartrtc_read( 3) * 10; |
132 | unsigned hour = memory_cartrtc_read( 4) + memory_cartrtc_read( 5) * 10; |
133 | unsigned day = memory_cartrtc_read( 6) + memory_cartrtc_read( 7) * 10; |
134 | unsigned month = memory_cartrtc_read( 8) + memory_cartrtc_read( 9) * 10; |
135 | unsigned year = memory_cartrtc_read(10) + memory_cartrtc_read(11) * 10; |
136 | unsigned weekday = memory_cartrtc_read(12); |
137 | |
138 | day--; |
139 | month--; |
140 | year += (year >= 90) ? 1900 : 2000; //range = 1990-2089 |
141 | |
142 | second += diff; |
143 | while(second >= 60) { |
144 | second -= 60; |
145 | |
146 | minute++; |
147 | if(minute < 60) continue; |
148 | minute = 0; |
149 | |
150 | hour++; |
151 | if(hour < 24) continue; |
152 | hour = 0; |
153 | |
154 | day++; |
155 | weekday = (weekday + 1) % 7; |
156 | unsigned days = months[month % 12]; |
157 | if(days == 28) { |
158 | bool leapyear = false; |
159 | if((year % 4) == 0) { |
160 | leapyear = true; |
161 | if((year % 100) == 0 && (year % 400) != 0) leapyear = false; |
162 | } |
163 | if(leapyear) days++; |
164 | } |
165 | if(day < days) continue; |
166 | day = 0; |
167 | |
168 | month++; |
169 | if(month < 12) continue; |
170 | month = 0; |
171 | |
172 | year++; |
173 | } |
174 | |
175 | day++; |
176 | month++; |
177 | year %= 100; |
178 | |
179 | memory_cartrtc_write( 0, second % 10); |
180 | memory_cartrtc_write( 1, second / 10); |
181 | memory_cartrtc_write( 2, minute % 10); |
182 | memory_cartrtc_write( 3, minute / 10); |
183 | memory_cartrtc_write( 4, hour % 10); |
184 | memory_cartrtc_write( 5, hour / 10); |
185 | memory_cartrtc_write( 6, day % 10); |
186 | memory_cartrtc_write( 7, day / 10); |
187 | memory_cartrtc_write( 8, month % 10); |
188 | memory_cartrtc_write( 9, month / 10); |
189 | memory_cartrtc_write(10, year % 10); |
190 | memory_cartrtc_write(11, (year / 10) % 10); |
191 | memory_cartrtc_write(12, weekday % 7); |
192 | } |
193 | |
194 | memory_cartrtc_write(16, current_time >> 0); |
195 | memory_cartrtc_write(17, current_time >> 8); |
196 | memory_cartrtc_write(18, current_time >> 16); |
197 | memory_cartrtc_write(19, current_time >> 24); |
198 | } |
199 | |
200 | uint8 SPC7110::mmio_read(unsigned addr) { |
201 | addr &= 0xffff; |
202 | |
203 | switch(addr) { |
204 | //================== |
205 | //decompression unit |
206 | //================== |
207 | |
208 | case 0x4800: { |
209 | uint16 counter = (r4809 + (r480a << 8)); |
210 | counter--; |
211 | r4809 = counter; |
212 | r480a = counter >> 8; |
213 | return decomp.read(); |
214 | } |
215 | case 0x4801: return r4801; |
216 | case 0x4802: return r4802; |
217 | case 0x4803: return r4803; |
218 | case 0x4804: return r4804; |
219 | case 0x4805: return r4805; |
220 | case 0x4806: return r4806; |
221 | case 0x4807: return r4807; |
222 | case 0x4808: return r4808; |
223 | case 0x4809: return r4809; |
224 | case 0x480a: return r480a; |
225 | case 0x480b: return r480b; |
226 | case 0x480c: { |
227 | uint8 status = r480c; |
228 | r480c &= 0x7f; |
229 | return status; |
230 | } |
231 | |
232 | //============== |
233 | //data port unit |
234 | //============== |
235 | |
236 | case 0x4810: { |
237 | if(r481x != 0x07) return 0x00; |
238 | |
239 | unsigned addr = data_pointer(); |
240 | unsigned adjust = data_adjust(); |
241 | if(r4818 & 8) adjust = (int16)adjust; //16-bit sign extend |
242 | |
243 | unsigned adjustaddr = addr; |
244 | if(r4818 & 2) { |
245 | adjustaddr += adjust; |
246 | set_data_adjust(adjust + 1); |
247 | } |
248 | |
249 | uint8 data = memory_cartrom_read(datarom_addr(adjustaddr)); |
250 | if(!(r4818 & 2)) { |
251 | unsigned increment = (r4818 & 1) ? data_increment() : 1; |
252 | if(r4818 & 4) increment = (int16)increment; //16-bit sign extend |
253 | |
254 | if((r4818 & 16) == 0) { |
255 | set_data_pointer(addr + increment); |
256 | } else { |
257 | set_data_adjust(adjust + increment); |
258 | } |
259 | } |
260 | |
261 | return data; |
262 | } |
263 | case 0x4811: return r4811; |
264 | case 0x4812: return r4812; |
265 | case 0x4813: return r4813; |
266 | case 0x4814: return r4814; |
267 | case 0x4815: return r4815; |
268 | case 0x4816: return r4816; |
269 | case 0x4817: return r4817; |
270 | case 0x4818: return r4818; |
271 | case 0x481a: { |
272 | if(r481x != 0x07) return 0x00; |
273 | |
274 | unsigned addr = data_pointer(); |
275 | unsigned adjust = data_adjust(); |
276 | if(r4818 & 8) adjust = (int16)adjust; //16-bit sign extend |
277 | |
278 | uint8 data = memory_cartrom_read(datarom_addr(addr + adjust)); |
279 | if((r4818 & 0x60) == 0x60) { |
280 | if((r4818 & 16) == 0) { |
281 | set_data_pointer(addr + adjust); |
282 | } else { |
283 | set_data_adjust(adjust + adjust); |
284 | } |
285 | } |
286 | |
287 | return data; |
288 | } |
289 | |
290 | //========= |
291 | //math unit |
292 | //========= |
293 | |
294 | case 0x4820: return r4820; |
295 | case 0x4821: return r4821; |
296 | case 0x4822: return r4822; |
297 | case 0x4823: return r4823; |
298 | case 0x4824: return r4824; |
299 | case 0x4825: return r4825; |
300 | case 0x4826: return r4826; |
301 | case 0x4827: return r4827; |
302 | case 0x4828: return r4828; |
303 | case 0x4829: return r4829; |
304 | case 0x482a: return r482a; |
305 | case 0x482b: return r482b; |
306 | case 0x482c: return r482c; |
307 | case 0x482d: return r482d; |
308 | case 0x482e: return r482e; |
309 | case 0x482f: { |
310 | uint8 status = r482f; |
311 | r482f &= 0x7f; |
312 | return status; |
313 | } |
314 | |
315 | //=================== |
316 | //memory mapping unit |
317 | //=================== |
318 | |
319 | case 0x4830: return r4830; |
320 | case 0x4831: return r4831; |
321 | case 0x4832: return r4832; |
322 | case 0x4833: return r4833; |
323 | case 0x4834: return r4834; |
324 | |
325 | //==================== |
326 | //real-time clock unit |
327 | //==================== |
328 | |
329 | case 0x4840: return r4840; |
330 | case 0x4841: { |
331 | if(rtc_state == RTCS_Inactive || rtc_state == RTCS_ModeSelect) return 0x00; |
332 | |
333 | r4842 = 0x80; |
334 | uint8 data = memory_cartrtc_read(rtc_index); |
335 | rtc_index = (rtc_index + 1) & 15; |
336 | return data; |
337 | } |
338 | case 0x4842: { |
339 | uint8 status = r4842; |
340 | r4842 &= 0x7f; |
341 | return status; |
342 | } |
343 | } |
344 | |
345 | return cpu_regs_mdr; |
346 | } |
347 | |
348 | void SPC7110::mmio_write(unsigned addr, uint8 data) { |
349 | addr &= 0xffff; |
350 | |
351 | switch(addr) { |
352 | //================== |
353 | //decompression unit |
354 | //================== |
355 | |
356 | case 0x4801: r4801 = data; break; |
357 | case 0x4802: r4802 = data; break; |
358 | case 0x4803: r4803 = data; break; |
359 | case 0x4804: r4804 = data; break; |
360 | case 0x4805: r4805 = data; break; |
361 | case 0x4806: { |
362 | r4806 = data; |
363 | |
364 | unsigned table = (r4801 + (r4802 << 8) + (r4803 << 16)); |
365 | unsigned index = (r4804 << 2); |
366 | //unsigned length = (r4809 + (r480a << 8)); |
367 | unsigned addr = datarom_addr(table + index); |
368 | unsigned mode = (memory_cartrom_read(addr + 0)); |
369 | unsigned offset = (memory_cartrom_read(addr + 1) << 16) |
370 | + (memory_cartrom_read(addr + 2) << 8) |
371 | + (memory_cartrom_read(addr + 3) << 0); |
372 | |
373 | decomp.init(mode, offset, (r4805 + (r4806 << 8)) << mode); |
374 | r480c = 0x80; |
375 | } break; |
376 | |
377 | case 0x4807: r4807 = data; break; |
378 | case 0x4808: r4808 = data; break; |
379 | case 0x4809: r4809 = data; break; |
380 | case 0x480a: r480a = data; break; |
381 | case 0x480b: r480b = data; break; |
382 | |
383 | //============== |
384 | //data port unit |
385 | //============== |
386 | |
387 | case 0x4811: r4811 = data; r481x |= 0x01; break; |
388 | case 0x4812: r4812 = data; r481x |= 0x02; break; |
389 | case 0x4813: r4813 = data; r481x |= 0x04; break; |
390 | case 0x4814: { |
391 | r4814 = data; |
392 | r4814_latch = true; |
393 | if(!r4815_latch) break; |
394 | if(!(r4818 & 2)) break; |
395 | if(r4818 & 0x10) break; |
396 | |
397 | if((r4818 & 0x60) == 0x20) { |
398 | unsigned increment = data_adjust() & 0xff; |
399 | if(r4818 & 8) increment = (int8)increment; //8-bit sign extend |
400 | set_data_pointer(data_pointer() + increment); |
401 | } else if((r4818 & 0x60) == 0x40) { |
402 | unsigned increment = data_adjust(); |
403 | if(r4818 & 8) increment = (int16)increment; //16-bit sign extend |
404 | set_data_pointer(data_pointer() + increment); |
405 | } |
406 | } break; |
407 | case 0x4815: { |
408 | r4815 = data; |
409 | r4815_latch = true; |
410 | if(!r4814_latch) break; |
411 | if(!(r4818 & 2)) break; |
412 | if(r4818 & 0x10) break; |
413 | |
414 | if((r4818 & 0x60) == 0x20) { |
415 | unsigned increment = data_adjust() & 0xff; |
416 | if(r4818 & 8) increment = (int8)increment; //8-bit sign extend |
417 | set_data_pointer(data_pointer() + increment); |
418 | } else if((r4818 & 0x60) == 0x40) { |
419 | unsigned increment = data_adjust(); |
420 | if(r4818 & 8) increment = (int16)increment; //16-bit sign extend |
421 | set_data_pointer(data_pointer() + increment); |
422 | } |
423 | } break; |
424 | case 0x4816: r4816 = data; break; |
425 | case 0x4817: r4817 = data; break; |
426 | case 0x4818: { |
427 | if(r481x != 0x07) break; |
428 | |
429 | r4818 = data; |
430 | r4814_latch = r4815_latch = false; |
431 | } break; |
432 | |
433 | //========= |
434 | //math unit |
435 | //========= |
436 | |
437 | case 0x4820: r4820 = data; break; |
438 | case 0x4821: r4821 = data; break; |
439 | case 0x4822: r4822 = data; break; |
440 | case 0x4823: r4823 = data; break; |
441 | case 0x4824: r4824 = data; break; |
442 | case 0x4825: { |
443 | r4825 = data; |
444 | |
445 | if(r482e & 1) { |
446 | //signed 16-bit x 16-bit multiplication |
447 | int16 r0 = (int16)(r4824 + (r4825 << 8)); |
448 | int16 r1 = (int16)(r4820 + (r4821 << 8)); |
449 | |
450 | signed result = r0 * r1; |
451 | r4828 = result; |
452 | r4829 = result >> 8; |
453 | r482a = result >> 16; |
454 | r482b = result >> 24; |
455 | } else { |
456 | //unsigned 16-bit x 16-bit multiplication |
457 | uint16 r0 = (uint16)(r4824 + (r4825 << 8)); |
458 | uint16 r1 = (uint16)(r4820 + (r4821 << 8)); |
459 | |
460 | unsigned result = r0 * r1; |
461 | r4828 = result; |
462 | r4829 = result >> 8; |
463 | r482a = result >> 16; |
464 | r482b = result >> 24; |
465 | } |
466 | |
467 | r482f = 0x80; |
468 | } break; |
469 | case 0x4826: r4826 = data; break; |
470 | case 0x4827: { |
471 | r4827 = data; |
472 | |
473 | if(r482e & 1) { |
474 | //signed 32-bit x 16-bit division |
475 | int32 dividend = (int32)(r4820 + (r4821 << 8) + (r4822 << 16) + (r4823 << 24)); |
476 | int16 divisor = (int16)(r4826 + (r4827 << 8)); |
477 | |
478 | int32 quotient; |
479 | int16 remainder; |
480 | |
481 | if(divisor) { |
482 | quotient = (int32)(dividend / divisor); |
483 | remainder = (int32)(dividend % divisor); |
484 | } else { |
485 | //illegal division by zero |
486 | quotient = 0; |
487 | remainder = dividend & 0xffff; |
488 | } |
489 | |
490 | r4828 = quotient; |
491 | r4829 = quotient >> 8; |
492 | r482a = quotient >> 16; |
493 | r482b = quotient >> 24; |
494 | |
495 | r482c = remainder; |
496 | r482d = remainder >> 8; |
497 | } else { |
498 | //unsigned 32-bit x 16-bit division |
499 | uint32 dividend = (uint32)(r4820 + (r4821 << 8) + (r4822 << 16) + (r4823 << 24)); |
500 | uint16 divisor = (uint16)(r4826 + (r4827 << 8)); |
501 | |
502 | uint32 quotient; |
503 | uint16 remainder; |
504 | |
505 | if(divisor) { |
506 | quotient = (uint32)(dividend / divisor); |
507 | remainder = (uint16)(dividend % divisor); |
508 | } else { |
509 | //illegal division by zero |
510 | quotient = 0; |
511 | remainder = dividend & 0xffff; |
512 | } |
513 | |
514 | r4828 = quotient; |
515 | r4829 = quotient >> 8; |
516 | r482a = quotient >> 16; |
517 | r482b = quotient >> 24; |
518 | |
519 | r482c = remainder; |
520 | r482d = remainder >> 8; |
521 | } |
522 | |
523 | r482f = 0x80; |
524 | } break; |
525 | |
526 | case 0x482e: { |
527 | //reset math unit |
528 | r4820 = r4821 = r4822 = r4823 = 0; |
529 | r4824 = r4825 = r4826 = r4827 = 0; |
530 | r4828 = r4829 = r482a = r482b = 0; |
531 | r482c = r482d = 0; |
532 | |
533 | r482e = data; |
534 | } break; |
535 | |
536 | //=================== |
537 | //memory mapping unit |
538 | //=================== |
539 | |
540 | case 0x4830: r4830 = data; break; |
541 | |
542 | case 0x4831: { |
543 | r4831 = data; |
544 | dx_offset = datarom_addr((data & 7) * 0x100000); |
545 | } break; |
546 | |
547 | case 0x4832: { |
548 | r4832 = data; |
549 | ex_offset = datarom_addr((data & 7) * 0x100000); |
550 | } break; |
551 | |
552 | case 0x4833: { |
553 | r4833 = data; |
554 | fx_offset = datarom_addr((data & 7) * 0x100000); |
555 | } break; |
556 | |
557 | case 0x4834: r4834 = data; break; |
558 | |
559 | //==================== |
560 | //real-time clock unit |
561 | //==================== |
562 | |
563 | case 0x4840: { |
564 | r4840 = data; |
565 | if(!(r4840 & 1)) { |
566 | //disable RTC |
567 | rtc_state = RTCS_Inactive; |
568 | update_time(); |
569 | } else { |
570 | //enable RTC |
571 | r4842 = 0x80; |
572 | rtc_state = RTCS_ModeSelect; |
573 | } |
574 | } break; |
575 | |
576 | case 0x4841: { |
577 | r4841 = data; |
578 | |
579 | switch(rtc_state) { |
580 | case RTCS_ModeSelect: { |
581 | if(data == RTCM_Linear || data == RTCM_Indexed) { |
582 | r4842 = 0x80; |
583 | rtc_state = RTCS_IndexSelect; |
584 | rtc_mode = (RTC_Mode)data; |
585 | rtc_index = 0; |
586 | } |
587 | } break; |
588 | |
589 | case RTCS_IndexSelect: { |
590 | r4842 = 0x80; |
591 | rtc_index = data & 15; |
592 | if(rtc_mode == RTCM_Linear) rtc_state = RTCS_Write; |
593 | } break; |
594 | |
595 | case RTCS_Write: { |
596 | r4842 = 0x80; |
597 | |
598 | //control register 0 |
599 | if(rtc_index == 13) { |
600 | //increment second counter |
601 | if(data & 2) update_time(+1); |
602 | |
603 | //round minute counter |
604 | if(data & 8) { |
605 | update_time(); |
606 | |
607 | unsigned second = memory_cartrtc_read( 0) + memory_cartrtc_read( 1) * 10; |
608 | //clear seconds |
609 | memory_cartrtc_write(0, 0); |
610 | memory_cartrtc_write(1, 0); |
611 | |
612 | if(second >= 30) update_time(+60); |
613 | } |
614 | } |
615 | |
616 | //control register 2 |
617 | if(rtc_index == 15) { |
618 | //disable timer and clear second counter |
619 | if((data & 1) && !(memory_cartrtc_read(15) & 1)) { |
620 | update_time(); |
621 | |
622 | //clear seconds |
623 | memory_cartrtc_write(0, 0); |
624 | memory_cartrtc_write(1, 0); |
625 | } |
626 | |
627 | //disable timer |
628 | if((data & 2) && !(memory_cartrtc_read(15) & 2)) { |
629 | update_time(); |
630 | } |
631 | } |
632 | |
633 | memory_cartrtc_write(rtc_index, data & 15); |
634 | rtc_index = (rtc_index + 1) & 15; |
635 | } break; |
636 | |
637 | case RTCS_Inactive: { |
638 | } break; |
639 | } //switch(rtc_state) |
640 | } break; |
641 | } |
642 | } |
643 | |
644 | SPC7110::SPC7110() { |
645 | } |
646 | |