ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/Frodo4/Src/CPUC64_SC.cpp
Revision: 1.7
Committed: 2009-01-11T11:21:01Z (15 years, 10 months ago) by cebix
Branch: MAIN
Changes since 1.6: +13 -7 lines
Log Message:
improved processor port emulation

File Contents

# User Rev Content
1 cebix 1.1 /*
2     * CPUC64_SC.cpp - Single-cycle 6510 (C64) emulation
3     *
4 cebix 1.7 * Frodo (C) 1994-1997,2002-2009 Christian Bauer
5 cebix 1.1 *
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19     */
20    
21     /*
22     * Notes:
23     * ------
24     *
25     * Opcode execution:
26     * - All opcodes are resolved into single clock cycles. There is one
27     * switch case for each cycle.
28     * - The "state" variable specifies the routine to be executed in the
29     * next cycle. Its upper 8 bits contain the current opcode, its lower
30     * 8 bits contain the cycle number (0..7) within the opcode.
31     * - Opcodes are fetched in cycle 0 (state = 0)
32     * - The states 0x0010..0x0027 are used for interrupts
33     * - There is exactly one memory access in each clock cycle
34     *
35     * Memory configurations:
36     *
37     * $01 $a000-$bfff $d000-$dfff $e000-$ffff
38     * -----------------------------------------------
39     * 0 RAM RAM RAM
40     * 1 RAM Char ROM RAM
41     * 2 RAM Char ROM Kernal ROM
42     * 3 Basic ROM Char ROM Kernal ROM
43     * 4 RAM RAM RAM
44     * 5 RAM I/O RAM
45     * 6 RAM I/O Kernal ROM
46     * 7 Basic ROM I/O Kernal ROM
47     *
48     * - All memory accesses are done with the read_byte() and
49     * write_byte() functions which also do the memory address
50     * decoding.
51     * - If a write occurs to addresses 0 or 1, new_config is
52     * called to check whether the memory configuration has
53     * changed
54     * - The possible interrupt sources are:
55     * INT_VICIRQ: I flag is checked, jump to ($fffe)
56     * INT_CIAIRQ: I flag is checked, jump to ($fffe)
57     * INT_NMI: Jump to ($fffa)
58     * INT_RESET: Jump to ($fffc)
59     * - The z_flag variable has the inverse meaning of the
60     * 6510 Z flag
61     * - Only the highest bit of the n_flag variable is used
62     * - The $f2 opcode that would normally crash the 6510 is
63     * used to implement emulator-specific functions, mainly
64     * those for the IEC routines
65     *
66     * Incompatibilities:
67     * ------------------
68     *
69     * - If BA is low and AEC is high, read accesses should occur
70     */
71    
72     #include "sysdeps.h"
73    
74     #include "CPUC64.h"
75     #include "CPU_common.h"
76     #include "C64.h"
77     #include "VIC.h"
78     #include "SID.h"
79     #include "CIA.h"
80     #include "REU.h"
81     #include "IEC.h"
82     #include "Display.h"
83     #include "Version.h"
84    
85    
86     enum {
87     INT_RESET = 3
88     };
89    
90    
91     /*
92     * 6510 constructor: Initialize registers
93     */
94    
95     MOS6510::MOS6510(C64 *c64, uint8 *Ram, uint8 *Basic, uint8 *Kernal, uint8 *Char, uint8 *Color)
96     : the_c64(c64), ram(Ram), basic_rom(Basic), kernal_rom(Kernal), char_rom(Char), color_ram(Color)
97     {
98     a = x = y = 0;
99     sp = 0xff;
100     n_flag = z_flag = 0;
101     v_flag = d_flag = c_flag = false;
102     i_flag = true;
103     dfff_byte = 0x55;
104     BALow = false;
105     first_irq_cycle = first_nmi_cycle = 0;
106     }
107    
108    
109     /*
110     * Reset CPU asynchronously
111     */
112    
113     void MOS6510::AsyncReset(void)
114     {
115     interrupt.intr[INT_RESET] = true;
116     }
117    
118    
119     /*
120     * Raise NMI asynchronously (Restore key)
121     */
122    
123     void MOS6510::AsyncNMI(void)
124     {
125     if (!nmi_state)
126     interrupt.intr[INT_NMI] = true;
127     }
128    
129    
130     /*
131     * Get 6510 register state
132     */
133    
134     void MOS6510::GetState(MOS6510State *s)
135     {
136     s->a = a;
137     s->x = x;
138     s->y = y;
139    
140     s->p = 0x20 | (n_flag & 0x80);
141     if (v_flag) s->p |= 0x40;
142     if (d_flag) s->p |= 0x08;
143     if (i_flag) s->p |= 0x04;
144     if (!z_flag) s->p |= 0x02;
145     if (c_flag) s->p |= 0x01;
146    
147     s->ddr = ddr;
148     s->pr = pr;
149    
150     s->pc = pc;
151     s->sp = sp | 0x0100;
152    
153     s->intr[INT_VICIRQ] = interrupt.intr[INT_VICIRQ];
154     s->intr[INT_CIAIRQ] = interrupt.intr[INT_CIAIRQ];
155     s->intr[INT_NMI] = interrupt.intr[INT_NMI];
156     s->intr[INT_RESET] = interrupt.intr[INT_RESET];
157     s->nmi_state = nmi_state;
158     s->dfff_byte = dfff_byte;
159     s->instruction_complete = (state == 0);
160     }
161    
162    
163     /*
164     * Restore 6510 state
165     */
166    
167     void MOS6510::SetState(MOS6510State *s)
168     {
169     a = s->a;
170     x = s->x;
171     y = s->y;
172    
173     n_flag = s->p;
174     v_flag = s->p & 0x40;
175     d_flag = s->p & 0x08;
176     i_flag = s->p & 0x04;
177     z_flag = !(s->p & 0x02);
178     c_flag = s->p & 0x01;
179    
180     ddr = s->ddr;
181     pr = s->pr;
182 cebix 1.7 pr_out = 0; // FIXME: should be saved in MOS6510State
183 cebix 1.1 new_config();
184    
185     pc = s->pc;
186     sp = s->sp & 0xff;
187    
188     interrupt.intr[INT_VICIRQ] = s->intr[INT_VICIRQ];
189     interrupt.intr[INT_CIAIRQ] = s->intr[INT_CIAIRQ];
190     interrupt.intr[INT_NMI] = s->intr[INT_NMI];
191     interrupt.intr[INT_RESET] = s->intr[INT_RESET];
192     nmi_state = s->nmi_state;
193     dfff_byte = s->dfff_byte;
194     if (s->instruction_complete)
195     state = 0;
196     }
197    
198    
199     /*
200     * Memory configuration has probably changed
201     */
202    
203     void MOS6510::new_config(void)
204     {
205 cebix 1.7 pr_out = (pr_out & ~ddr) | (pr & ddr);
206     uint8 port = pr | ~ddr;
207 cebix 1.1
208     basic_in = (port & 3) == 3;
209     kernal_in = port & 2;
210     char_in = (port & 3) && !(port & 4);
211     io_in = (port & 3) && (port & 4);
212     }
213    
214    
215     /*
216     * Read a byte from I/O / ROM space
217     */
218    
219     inline uint8 MOS6510::read_byte_io(uint16 adr)
220     {
221     switch (adr >> 12) {
222     case 0xa:
223     case 0xb:
224     if (basic_in)
225     return basic_rom[adr & 0x1fff];
226     else
227     return ram[adr];
228     case 0xc:
229     return ram[adr];
230     case 0xd:
231     if (io_in)
232     switch ((adr >> 8) & 0x0f) {
233     case 0x0: // VIC
234     case 0x1:
235     case 0x2:
236     case 0x3:
237     return TheVIC->ReadRegister(adr & 0x3f);
238     case 0x4: // SID
239     case 0x5:
240     case 0x6:
241     case 0x7:
242     return TheSID->ReadRegister(adr & 0x1f);
243     case 0x8: // Color RAM
244     case 0x9:
245     case 0xa:
246     case 0xb:
247     return color_ram[adr & 0x03ff] & 0x0f | TheVIC->LastVICByte & 0xf0;
248     case 0xc: // CIA 1
249     return TheCIA1->ReadRegister(adr & 0x0f);
250     case 0xd: // CIA 2
251     return TheCIA2->ReadRegister(adr & 0x0f);
252     case 0xe: // REU/Open I/O
253     case 0xf:
254     if ((adr & 0xfff0) == 0xdf00)
255     return TheREU->ReadRegister(adr & 0x0f);
256     else if (adr < 0xdfa0)
257     return TheVIC->LastVICByte;
258     else
259     return read_emulator_id(adr & 0x7f);
260     }
261     else if (char_in)
262     return char_rom[adr & 0x0fff];
263     else
264     return ram[adr];
265     case 0xe:
266     case 0xf:
267     if (kernal_in)
268     return kernal_rom[adr & 0x1fff];
269     else
270     return ram[adr];
271     default: // Can't happen
272     return 0;
273     }
274     }
275    
276    
277     /*
278     * Read a byte from the CPU's address space
279     */
280    
281     uint8 MOS6510::read_byte(uint16 adr)
282     {
283     if (adr < 0xa000) {
284 cebix 1.7 if (adr >= 2) {
285 cebix 1.1 return ram[adr];
286 cebix 1.7 } else if (adr == 0) {
287 cebix 1.1 return ddr;
288 cebix 1.7 } else {
289     uint8 byte = (pr | ~ddr) & (pr_out | 0x17);
290     if (!(ddr & 0x20))
291     byte &= 0xdf;
292     return byte;
293     }
294 cebix 1.1 } else
295     return read_byte_io(adr);
296     }
297    
298    
299     /*
300     * $dfa0-$dfff: Emulator identification
301     */
302    
303     const char frodo_id[0x5c] = "FRODO\r(C) 1994-1997 CHRISTIAN BAUER";
304    
305     uint8 MOS6510::read_emulator_id(uint16 adr)
306     {
307     switch (adr) {
308     case 0x7c: // $dffc: revision
309     return FRODO_REVISION << 4;
310     case 0x7d: // $dffd: version
311     return FRODO_VERSION;
312     case 0x7e: // $dffe returns 'F' (Frodo ID)
313     return 'F';
314     case 0x7f: // $dfff alternates between $55 and $aa
315     dfff_byte = ~dfff_byte;
316     return dfff_byte;
317     default:
318     return frodo_id[adr - 0x20];
319     }
320     }
321    
322    
323     /*
324     * Read a word (little-endian) from the CPU's address space
325     */
326    
327     inline uint16 MOS6510::read_word(uint16 adr)
328     {
329     return read_byte(adr) | (read_byte(adr+1) << 8);
330     }
331    
332    
333     /*
334     * Write a byte to I/O space
335     */
336    
337     inline void MOS6510::write_byte_io(uint16 adr, uint8 byte)
338     {
339     if (adr >= 0xe000) {
340     ram[adr] = byte;
341     if (adr == 0xff00)
342     TheREU->FF00Trigger();
343     } else if (io_in)
344     switch ((adr >> 8) & 0x0f) {
345     case 0x0: // VIC
346     case 0x1:
347     case 0x2:
348     case 0x3:
349     TheVIC->WriteRegister(adr & 0x3f, byte);
350     return;
351     case 0x4: // SID
352     case 0x5:
353     case 0x6:
354     case 0x7:
355     TheSID->WriteRegister(adr & 0x1f, byte);
356     return;
357     case 0x8: // Color RAM
358     case 0x9:
359     case 0xa:
360     case 0xb:
361     color_ram[adr & 0x03ff] = byte & 0x0f;
362     return;
363     case 0xc: // CIA 1
364     TheCIA1->WriteRegister(adr & 0x0f, byte);
365     return;
366     case 0xd: // CIA 2
367     TheCIA2->WriteRegister(adr & 0x0f, byte);
368     return;
369     case 0xe: // REU/Open I/O
370     case 0xf:
371     if ((adr & 0xfff0) == 0xdf00)
372     TheREU->WriteRegister(adr & 0x0f, byte);
373     return;
374     }
375     else
376     ram[adr] = byte;
377     }
378    
379    
380     /*
381     * Write a byte to the CPU's address space
382     */
383    
384     void MOS6510::write_byte(uint16 adr, uint8 byte)
385     {
386     if (adr < 0xd000) {
387     if (adr >= 2)
388     ram[adr] = byte;
389     else if (adr == 0) {
390     ddr = byte;
391     ram[0] = TheVIC->LastVICByte;
392     new_config();
393     } else {
394     pr = byte;
395     ram[1] = TheVIC->LastVICByte;
396     new_config();
397     }
398     } else
399     write_byte_io(adr, byte);
400     }
401    
402    
403     /*
404     * Read byte from 6510 address space with special memory config (used by SAM)
405     */
406    
407     uint8 MOS6510::ExtReadByte(uint16 adr)
408     {
409     // Save old memory configuration
410     bool bi = basic_in, ki = kernal_in, ci = char_in, ii = io_in;
411    
412     // Set new configuration
413     basic_in = (ExtConfig & 3) == 3;
414     kernal_in = ExtConfig & 2;
415     char_in = (ExtConfig & 3) && ~(ExtConfig & 4);
416     io_in = (ExtConfig & 3) && (ExtConfig & 4);
417    
418     // Read byte
419     uint8 byte = read_byte(adr);
420    
421     // Restore old configuration
422     basic_in = bi; kernal_in = ki; char_in = ci; io_in = ii;
423    
424     return byte;
425     }
426    
427    
428     /*
429     * Write byte to 6510 address space with special memory config (used by SAM)
430     */
431    
432     void MOS6510::ExtWriteByte(uint16 adr, uint8 byte)
433     {
434     // Save old memory configuration
435     bool bi = basic_in, ki = kernal_in, ci = char_in, ii = io_in;
436    
437     // Set new configuration
438     basic_in = (ExtConfig & 3) == 3;
439     kernal_in = ExtConfig & 2;
440     char_in = (ExtConfig & 3) && ~(ExtConfig & 4);
441     io_in = (ExtConfig & 3) && (ExtConfig & 4);
442    
443     // Write byte
444     write_byte(adr, byte);
445    
446     // Restore old configuration
447     basic_in = bi; kernal_in = ki; char_in = ci; io_in = ii;
448     }
449    
450    
451     /*
452     * Read byte from 6510 address space with current memory config (used by REU)
453     */
454    
455     uint8 MOS6510::REUReadByte(uint16 adr)
456     {
457     return read_byte(adr);
458     }
459    
460    
461     /*
462     * Write byte to 6510 address space with current memory config (used by REU)
463     */
464    
465     void MOS6510::REUWriteByte(uint16 adr, uint8 byte)
466     {
467     write_byte(adr, byte);
468     }
469    
470    
471     /*
472     * Adc instruction
473     */
474    
475     inline void MOS6510::do_adc(uint8 byte)
476     {
477     if (!d_flag) {
478     uint16 tmp;
479    
480     // Binary mode
481     tmp = a + byte + (c_flag ? 1 : 0);
482     c_flag = tmp > 0xff;
483     v_flag = !((a ^ byte) & 0x80) && ((a ^ tmp) & 0x80);
484     z_flag = n_flag = a = tmp;
485    
486     } else {
487     uint16 al, ah;
488    
489     // Decimal mode
490     al = (a & 0x0f) + (byte & 0x0f) + (c_flag ? 1 : 0); // Calculate lower nybble
491     if (al > 9) al += 6; // BCD fixup for lower nybble
492    
493     ah = (a >> 4) + (byte >> 4); // Calculate upper nybble
494     if (al > 0x0f) ah++;
495    
496     z_flag = a + byte + (c_flag ? 1 : 0); // Set flags
497     n_flag = ah << 4; // Only highest bit used
498     v_flag = (((ah << 4) ^ a) & 0x80) && !((a ^ byte) & 0x80);
499    
500     if (ah > 9) ah += 6; // BCD fixup for upper nybble
501     c_flag = ah > 0x0f; // Set carry flag
502     a = (ah << 4) | (al & 0x0f); // Compose result
503     }
504     }
505    
506    
507     /*
508     * Sbc instruction
509     */
510    
511     inline void MOS6510::do_sbc(uint8 byte)
512     {
513     uint16 tmp = a - byte - (c_flag ? 0 : 1);
514    
515     if (!d_flag) {
516    
517     // Binary mode
518     c_flag = tmp < 0x100;
519     v_flag = ((a ^ tmp) & 0x80) && ((a ^ byte) & 0x80);
520     z_flag = n_flag = a = tmp;
521    
522     } else {
523     uint16 al, ah;
524    
525     // Decimal mode
526     al = (a & 0x0f) - (byte & 0x0f) - (c_flag ? 0 : 1); // Calculate lower nybble
527     ah = (a >> 4) - (byte >> 4); // Calculate upper nybble
528     if (al & 0x10) {
529     al -= 6; // BCD fixup for lower nybble
530     ah--;
531     }
532     if (ah & 0x10) ah -= 6; // BCD fixup for upper nybble
533    
534     c_flag = tmp < 0x100; // Set flags
535     v_flag = ((a ^ tmp) & 0x80) && ((a ^ byte) & 0x80);
536     z_flag = n_flag = tmp;
537    
538     a = (ah << 4) | (al & 0x0f); // Compose result
539     }
540     }
541    
542    
543     /*
544     * Reset CPU
545     */
546    
547     void MOS6510::Reset(void)
548     {
549     // Delete 'CBM80' if present
550     if (ram[0x8004] == 0xc3 && ram[0x8005] == 0xc2 && ram[0x8006] == 0xcd
551     && ram[0x8007] == 0x38 && ram[0x8008] == 0x30)
552     ram[0x8004] = 0;
553    
554     // Initialize extra 6510 registers and memory configuration
555 cebix 1.7 ddr = pr = pr_out = 0;
556 cebix 1.1 new_config();
557    
558     // Clear all interrupt lines
559     interrupt.intr_any = 0;
560     nmi_state = false;
561    
562     // Read reset vector
563     pc = read_word(0xfffc);
564     state = 0;
565     }
566    
567    
568     /*
569     * Illegal opcode encountered
570     */
571    
572     void MOS6510::illegal_op(uint8 op, uint16 at)
573     {
574     char illop_msg[80];
575    
576     sprintf(illop_msg, "Illegal opcode %02x at %04x.", op, at);
577     ShowRequester(illop_msg, "Reset");
578     the_c64->Reset();
579     Reset();
580     }
581    
582    
583     /*
584     * Emulate one 6510 clock cycle
585     */
586    
587     // Read byte from memory
588     #define read_to(adr, to) \
589     if (BALow) \
590     return; \
591     to = read_byte(adr);
592    
593     // Read byte from memory, throw away result
594     #define read_idle(adr) \
595     if (BALow) \
596     return; \
597     read_byte(adr);
598    
599     void MOS6510::EmulateCycle(void)
600     {
601     uint8 data, tmp;
602    
603     // Any pending interrupts in state 0 (opcode fetch)?
604     if (!state && interrupt.intr_any) {
605     if (interrupt.intr[INT_RESET])
606     Reset();
607     else if (interrupt.intr[INT_NMI] && (the_c64->CycleCounter-first_nmi_cycle >= 2)) {
608     interrupt.intr[INT_NMI] = false; // Simulate an edge-triggered input
609     state = 0x0010;
610     } else if ((interrupt.intr[INT_VICIRQ] || interrupt.intr[INT_CIAIRQ]) && (the_c64->CycleCounter-first_irq_cycle >= 2) && !i_flag)
611     state = 0x0008;
612     }
613    
614     #include "CPU_emulcycle.h"
615    
616     // Extension opcode
617     case O_EXT:
618     if (pc < 0xe000) {
619     illegal_op(0xf2, pc-1);
620     break;
621     }
622     switch (read_byte(pc++)) {
623     case 0x00:
624     ram[0x90] |= TheIEC->Out(ram[0x95], ram[0xa3] & 0x80);
625     c_flag = false;
626     pc = 0xedac;
627     Last;
628     case 0x01:
629     ram[0x90] |= TheIEC->OutATN(ram[0x95]);
630     c_flag = false;
631     pc = 0xedac;
632     Last;
633     case 0x02:
634     ram[0x90] |= TheIEC->OutSec(ram[0x95]);
635     c_flag = false;
636     pc = 0xedac;
637     Last;
638     case 0x03:
639 cebix 1.4 ram[0x90] |= TheIEC->In(a);
640 cebix 1.1 set_nz(a);
641     c_flag = false;
642     pc = 0xedac;
643     Last;
644     case 0x04:
645     TheIEC->SetATN();
646     pc = 0xedfb;
647     Last;
648     case 0x05:
649     TheIEC->RelATN();
650     pc = 0xedac;
651     Last;
652     case 0x06:
653     TheIEC->Turnaround();
654     pc = 0xedac;
655     Last;
656     case 0x07:
657     TheIEC->Release();
658     pc = 0xedac;
659     Last;
660     default:
661     illegal_op(0xf2, pc-1);
662     break;
663     }
664     break;
665    
666     default:
667     illegal_op(op, pc-1);
668     break;
669     }
670     }