/* * CPU1541.cpp - 6502 (1541) emulation (line based) * * Frodo (C) 1994-1997,2002-2004 Christian Bauer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * Notes: * ------ * * - The EmulateLine() function is called for every emulated * raster line. It has a cycle counter that is decremented * by every executed opcode and if the counter goes below * zero, the function returns. * - Memory map (1541C, the 1541 and 1541-II are a bit different): * $0000-$07ff RAM (2K) * $0800-$0fff RAM mirror * $1000-$17ff free * $1800-$1bff VIA 1 * $1c00-$1fff VIA 2 * $2000-$bfff free * $c000-$ffff ROM (16K) * - All memory accesses are done with the read_byte() and * write_byte() functions which also do the memory address * decoding. The read_zp() and write_zp() functions allow * faster access to the zero page, the pop_byte() and * push_byte() macros for the stack. * - The PC is either emulated with a 16 bit address or a * direct memory pointer (for faster access), depending on * the PC_IS_POINTER #define. In the latter case, a second * pointer, pc_base, is kept to allow recalculating the * 16 bit 6502 PC if it has to be pushed on the stack. * - The possible interrupt sources are: * INT_VIA1IRQ: I flag is checked, jump to ($fffe) (unused) * INT_VIA2IRQ: I flag is checked, jump to ($fffe) (unused) * INT_IECIRQ: I flag is checked, jump to ($fffe) (unused) * INT_RESET: Jump to ($fffc) * - Interrupts are not checked before every opcode but only * at certain times: * On entering EmulateLine() * On CLI * On PLP if the I flag was cleared * On RTI if the I flag was cleared * - The z_flag variable has the inverse meaning of the * 6502 Z flag * - Only the highest bit of the n_flag variable is used * - The $f2 opcode that would normally crash the 6502 is * used to implement emulator-specific functions * - The 1541 6502 emulation also includes a very simple VIA * emulation (enough to make the IEC bus and GCR loading work). * It's too small to move it to a source file of its own. * * Incompatibilities: * ------------------ * * - If PC_IS_POINTER is set, neither branches accross memory * areas nor jumps to I/O space are possible * - Extra cycles for crossing page boundaries are not * accounted for */ #include "sysdeps.h" #include "CPU1541.h" #include "1541job.h" #include "C64.h" #include "CIA.h" #include "Display.h" enum { INT_RESET = 3 }; /* * 6502 constructor: Initialize registers */ MOS6502_1541::MOS6502_1541(C64 *c64, Job1541 *job, C64Display *disp, uint8 *Ram, uint8 *Rom) : ram(Ram), rom(Rom), the_c64(c64), the_display(disp), the_job(job) { a = x = y = 0; sp = 0xff; n_flag = z_flag = 0; v_flag = d_flag = c_flag = false; i_flag = true; borrowed_cycles = 0; via1_t1c = via1_t1l = via1_t2c = via1_t2l = 0; via1_sr = 0; via2_t1c = via2_t1l = via2_t2c = via2_t2l = 0; via2_sr = 0; Idle = false; } /* * Reset CPU asynchronously */ void MOS6502_1541::AsyncReset(void) { interrupt.intr[INT_RESET] = true; Idle = false; } /* * Read a byte from I/O space */ inline uint8 MOS6502_1541::read_byte_io(uint16 adr) { if ((adr & 0xfc00) == 0x1800) // VIA 1 switch (adr & 0xf) { case 0: return (via1_prb & 0x1a | ((IECLines & TheCIA2->IECLines) >> 7) // DATA | ((IECLines & TheCIA2->IECLines) >> 4) & 0x04 // CLK | (TheCIA2->IECLines << 3) & 0x80) ^ 0x85; // ATN case 1: case 15: return 0xff; // Keep 1541C ROMs happy (track 0 sensor) case 2: return via1_ddrb; case 3: return via1_ddra; case 4: via1_ifr &= 0xbf; return via1_t1c; case 5: return via1_t1c >> 8; case 6: return via1_t1l; case 7: return via1_t1l >> 8; case 8: via1_ifr &= 0xdf; return via1_t2c; case 9: return via1_t2c >> 8; case 10: return via1_sr; case 11: return via1_acr; case 12: return via1_pcr; case 13: return via1_ifr | (via1_ifr & via1_ier ? 0x80 : 0); case 14: return via1_ier | 0x80; default: // Can't happen return 0; } else if ((adr & 0xfc00) == 0x1c00) // VIA 2 switch (adr & 0xf) { case 0: if (the_job->SyncFound()) return via2_prb & 0x7f | the_job->WPState(); else return via2_prb | 0x80 | the_job->WPState(); case 1: case 15: return the_job->ReadGCRByte(); case 2: return via2_ddrb; case 3: return via2_ddra; case 4: via2_ifr &= 0xbf; interrupt.intr[INT_VIA2IRQ] = false; // Clear job IRQ return via2_t1c; case 5: return via2_t1c >> 8; case 6: return via2_t1l; case 7: return via2_t1l >> 8; case 8: via2_ifr &= 0xdf; return via2_t2c; case 9: return via2_t2c >> 8; case 10: return via2_sr; case 11: return via2_acr; case 12: return via2_pcr; case 13: return via2_ifr | (via2_ifr & via2_ier ? 0x80 : 0); case 14: return via2_ier | 0x80; default: // Can't happen return 0; } else return adr >> 8; } /* * Read a byte from the CPU's address space */ uint8 MOS6502_1541::read_byte(uint16 adr) { if (adr >= 0xc000) return rom[adr & 0x3fff]; else if (adr < 0x1000) return ram[adr & 0x07ff]; else return read_byte_io(adr); } /* * Read a word (little-endian) from the CPU's address space */ inline uint16 MOS6502_1541::read_word(uint16 adr) { return read_byte(adr) | (read_byte(adr+1) << 8); } /* * Write a byte to I/O space */ void MOS6502_1541::write_byte_io(uint16 adr, uint8 byte) { if ((adr & 0xfc00) == 0x1800) // VIA 1 switch (adr & 0xf) { case 0: via1_prb = byte; byte = ~via1_prb & via1_ddrb; IECLines = (byte << 6) & ((~byte ^ TheCIA2->IECLines) << 3) & 0x80 | (byte << 3) & 0x40; break; case 1: case 15: via1_pra = byte; break; case 2: via1_ddrb = byte; byte &= ~via1_prb; IECLines = (byte << 6) & ((~byte ^ TheCIA2->IECLines) << 3) & 0x80 | (byte << 3) & 0x40; break; case 3: via1_ddra = byte; break; case 4: case 6: via1_t1l = via1_t1l & 0xff00 | byte; break; case 5: via1_t1l = via1_t1l & 0xff | (byte << 8); via1_ifr &= 0xbf; via1_t1c = via1_t1l; break; case 7: via1_t1l = via1_t1l & 0xff | (byte << 8); break; case 8: via1_t2l = via1_t2l & 0xff00 | byte; break; case 9: via1_t2l = via1_t2l & 0xff | (byte << 8); via1_ifr &= 0xdf; via1_t2c = via1_t2l; break; case 10: via1_sr = byte; break; case 11: via1_acr = byte; break; case 12: via1_pcr = byte; break; case 13: via1_ifr &= ~byte; break; case 14: if (byte & 0x80) via1_ier |= byte & 0x7f; else via1_ier &= ~byte; break; } else if ((adr & 0xfc00) == 0x1c00) // VIA 2 switch (adr & 0xf) { case 0: if ((via2_prb ^ byte) & 8) // Bit 3: Drive LED the_display->UpdateLEDs(byte & 8 ? 1 : 0, 0, 0, 0); if ((via2_prb ^ byte) & 3) // Bits 0/1: Stepper motor if ((via2_prb & 3) == ((byte+1) & 3)) the_job->MoveHeadOut(); else if ((via2_prb & 3) == ((byte-1) & 3)) the_job->MoveHeadIn(); via2_prb = byte & 0xef; break; case 1: case 15: via2_pra = byte; break; case 2: via2_ddrb = byte; break; case 3: via2_ddra = byte; break; case 4: case 6: via2_t1l = via2_t1l & 0xff00 | byte; break; case 5: via2_t1l = via2_t1l & 0xff | (byte << 8); via2_ifr &= 0xbf; via2_t1c = via2_t1l; break; case 7: via2_t1l = via2_t1l & 0xff | (byte << 8); break; case 8: via2_t2l = via2_t2l & 0xff00 | byte; break; case 9: via2_t2l = via2_t2l & 0xff | (byte << 8); via2_ifr &= 0xdf; via2_t2c = via2_t2l; break; case 10: via2_sr = byte; break; case 11: via2_acr = byte; break; case 12: via2_pcr = byte; break; case 13: via2_ifr &= ~byte; break; case 14: if (byte & 0x80) via2_ier |= byte & 0x7f; else via2_ier &= ~byte; break; } } /* * Write a byte to the CPU's address space */ inline void MOS6502_1541::write_byte(uint16 adr, uint8 byte) { if (adr < 0x1000) ram[adr & 0x7ff] = byte; else write_byte_io(adr, byte); } /* * Read a byte from the zeropage */ inline uint8 MOS6502_1541::read_zp(uint16 adr) { return ram[adr]; } /* * Read a word (little-endian) from the zeropage */ inline uint16 MOS6502_1541::read_zp_word(uint16 adr) { return ram[adr & 0xff] | (ram[(adr+1) & 0xff] << 8); } /* * Write a byte to the zeropage */ inline void MOS6502_1541::write_zp(uint16 adr, uint8 byte) { ram[adr] = byte; } /* * Read byte from 6502/1541 address space (used by SAM) */ uint8 MOS6502_1541::ExtReadByte(uint16 adr) { return read_byte(adr); } /* * Write byte to 6502/1541 address space (used by SAM) */ void MOS6502_1541::ExtWriteByte(uint16 adr, uint8 byte) { write_byte(adr, byte); } /* * Jump to address */ #if PC_IS_POINTER void MOS6502_1541::jump(uint16 adr) { if (adr >= 0xc000) { pc = rom + (adr & 0x3fff); pc_base = rom - 0xc000; } else if (adr < 0x800) { pc = ram + adr; pc_base = ram; } else illegal_jump(pc-pc_base, adr); } #else inline void MOS6502_1541::jump(uint16 adr) { pc = adr; } #endif /* * Adc instruction */ void MOS6502_1541::do_adc(uint8 byte) { if (!d_flag) { uint16 tmp; // Binary mode tmp = a + byte + (c_flag ? 1 : 0); c_flag = tmp > 0xff; v_flag = !((a ^ byte) & 0x80) && ((a ^ tmp) & 0x80); z_flag = n_flag = a = tmp; } else { uint16 al, ah; // Decimal mode al = (a & 0x0f) + (byte & 0x0f) + (c_flag ? 1 : 0); // Calculate lower nybble if (al > 9) al += 6; // BCD fixup for lower nybble ah = (a >> 4) + (byte >> 4); // Calculate upper nybble if (al > 0x0f) ah++; z_flag = a + byte + (c_flag ? 1 : 0); // Set flags n_flag = ah << 4; // Only highest bit used v_flag = (((ah << 4) ^ a) & 0x80) && !((a ^ byte) & 0x80); if (ah > 9) ah += 6; // BCD fixup for upper nybble c_flag = ah > 0x0f; // Set carry flag a = (ah << 4) | (al & 0x0f); // Compose result } } /* * Sbc instruction */ void MOS6502_1541::do_sbc(uint8 byte) { uint16 tmp = a - byte - (c_flag ? 0 : 1); if (!d_flag) { // Binary mode c_flag = tmp < 0x100; v_flag = ((a ^ tmp) & 0x80) && ((a ^ byte) & 0x80); z_flag = n_flag = a = tmp; } else { uint16 al, ah; // Decimal mode al = (a & 0x0f) - (byte & 0x0f) - (c_flag ? 0 : 1); // Calculate lower nybble ah = (a >> 4) - (byte >> 4); // Calculate upper nybble if (al & 0x10) { al -= 6; // BCD fixup for lower nybble ah--; } if (ah & 0x10) ah -= 6; // BCD fixup for upper nybble c_flag = tmp < 0x100; // Set flags v_flag = ((a ^ tmp) & 0x80) && ((a ^ byte) & 0x80); z_flag = n_flag = tmp; a = (ah << 4) | (al & 0x0f); // Compose result } } /* * Get 6502 register state */ void MOS6502_1541::GetState(MOS6502State *s) { s->a = a; s->x = x; s->y = y; s->p = 0x20 | (n_flag & 0x80); if (v_flag) s->p |= 0x40; if (d_flag) s->p |= 0x08; if (i_flag) s->p |= 0x04; if (!z_flag) s->p |= 0x02; if (c_flag) s->p |= 0x01; #if PC_IS_POINTER s->pc = pc - pc_base; #else s->pc = pc; #endif s->sp = sp | 0x0100; s->intr[INT_VIA1IRQ] = interrupt.intr[INT_VIA1IRQ]; s->intr[INT_VIA2IRQ] = interrupt.intr[INT_VIA2IRQ]; s->intr[INT_IECIRQ] = interrupt.intr[INT_IECIRQ]; s->intr[INT_RESET] = interrupt.intr[INT_RESET]; s->instruction_complete = true; s->idle = Idle; s->via1_pra = via1_pra; s->via1_ddra = via1_ddra; s->via1_prb = via1_prb; s->via1_ddrb = via1_ddrb; s->via1_t1c = via1_t1c; s->via1_t1l = via1_t1l; s->via1_t2c = via1_t2c; s->via1_t2l = via1_t2l; s->via1_sr = via1_sr; s->via1_acr = via1_acr; s->via1_pcr = via1_pcr; s->via1_ifr = via1_ifr; s->via1_ier = via1_ier; s->via2_pra = via2_pra; s->via2_ddra = via2_ddra; s->via2_prb = via2_prb; s->via2_ddrb = via2_ddrb; s->via2_t1c = via2_t1c; s->via2_t1l = via2_t1l; s->via2_t2c = via2_t2c; s->via2_t2l = via2_t2l; s->via2_sr = via2_sr; s->via2_acr = via2_acr; s->via2_pcr = via2_pcr; s->via2_ifr = via2_ifr; s->via2_ier = via2_ier; } /* * Restore 6502 state */ void MOS6502_1541::SetState(MOS6502State *s) { a = s->a; x = s->x; y = s->y; n_flag = s->p; v_flag = s->p & 0x40; d_flag = s->p & 0x08; i_flag = s->p & 0x04; z_flag = !(s->p & 0x02); c_flag = s->p & 0x01; jump(s->pc); sp = s->sp & 0xff; interrupt.intr[INT_VIA1IRQ] = s->intr[INT_VIA1IRQ]; interrupt.intr[INT_VIA2IRQ] = s->intr[INT_VIA2IRQ]; interrupt.intr[INT_IECIRQ] = s->intr[INT_IECIRQ]; interrupt.intr[INT_RESET] = s->intr[INT_RESET]; Idle = s->idle; via1_pra = s->via1_pra; via1_ddra = s->via1_ddra; via1_prb = s->via1_prb; via1_ddrb = s->via1_ddrb; via1_t1c = s->via1_t1c; via1_t1l = s->via1_t1l; via1_t2c = s->via1_t2c; via1_t2l = s->via1_t2l; via1_sr = s->via1_sr; via1_acr = s->via1_acr; via1_pcr = s->via1_pcr; via1_ifr = s->via1_ifr; via1_ier = s->via1_ier; via2_pra = s->via2_pra; via2_ddra = s->via2_ddra; via2_prb = s->via2_prb; via2_ddrb = s->via2_ddrb; via2_t1c = s->via2_t1c; via2_t1l = s->via2_t1l; via2_t2c = s->via2_t2c; via2_t2l = s->via2_t2l; via2_sr = s->via2_sr; via2_acr = s->via2_acr; via2_pcr = s->via2_pcr; via2_ifr = s->via2_ifr; via2_ier = s->via2_ier; } /* * Reset CPU */ void MOS6502_1541::Reset(void) { // IEC lines and VIA registers IECLines = 0xc0; via1_pra = via1_ddra = via1_prb = via1_ddrb = 0; via1_acr = via1_pcr = 0; via1_ifr = via1_ier = 0; via2_pra = via2_ddra = via2_prb = via2_ddrb = 0; via2_acr = via2_pcr = 0; via2_ifr = via2_ier = 0; // Clear all interrupt lines interrupt.intr_any = 0; // Read reset vector jump(read_word(0xfffc)); // Wake up 1541 Idle = false; } /* * Illegal opcode encountered */ void MOS6502_1541::illegal_op(uint8 op, uint16 at) { char illop_msg[80]; sprintf(illop_msg, "1541: Illegal opcode %02x at %04x.", op, at); if (ShowRequester(illop_msg, "Reset 1541", "Reset C64")) the_c64->Reset(); Reset(); } /* * Jump to illegal address space (PC_IS_POINTER only) */ void MOS6502_1541::illegal_jump(uint16 at, uint16 to) { char illop_msg[80]; sprintf(illop_msg, "1541: Jump to I/O space at %04x to %04x.", at, to); if (ShowRequester(illop_msg, "Reset 1541", "Reset C64")) the_c64->Reset(); Reset(); } /* * Stack macros */ // Pop a byte from the stack #define pop_byte() ram[(++sp) | 0x0100] // Push a byte onto the stack #define push_byte(byte) (ram[(sp--) & 0xff | 0x0100] = (byte)) // Pop processor flags from the stack #define pop_flags() \ n_flag = tmp = pop_byte(); \ v_flag = tmp & 0x40; \ d_flag = tmp & 0x08; \ i_flag = tmp & 0x04; \ z_flag = !(tmp & 0x02); \ c_flag = tmp & 0x01; // Push processor flags onto the stack #define push_flags(b_flag) \ tmp = 0x20 | (n_flag & 0x80); \ if (v_flag) tmp |= 0x40; \ if (b_flag) tmp |= 0x10; \ if (d_flag) tmp |= 0x08; \ if (i_flag) tmp |= 0x04; \ if (!z_flag) tmp |= 0x02; \ if (c_flag) tmp |= 0x01; \ push_byte(tmp); /* * Emulate cycles_left worth of 6502 instructions * Returns number of cycles of last instruction */ int MOS6502_1541::EmulateLine(int cycles_left) { uint8 tmp, tmp2; uint16 adr; int last_cycles = 0; // Any pending interrupts? if (interrupt.intr_any) { handle_int: if (interrupt.intr[INT_RESET]) Reset(); else if ((interrupt.intr[INT_VIA1IRQ] || interrupt.intr[INT_VIA2IRQ] || interrupt.intr[INT_IECIRQ]) && !i_flag) { #if PC_IS_POINTER push_byte((pc-pc_base) >> 8); push_byte(pc-pc_base); #else push_byte(pc >> 8); push_byte(pc); #endif push_flags(false); i_flag = true; jump(read_word(0xfffe)); last_cycles = 7; } } #define IS_CPU_1541 #include "CPU_emulline.h" // Extension opcode case 0xf2: #if PC_IS_POINTER if ((pc-pc_base) < 0xc000) { illegal_op(0xf2, pc-pc_base-1); #else if (pc < 0xc000) { illegal_op(0xf2, pc-1); #endif break; } switch (read_byte_imm()) { case 0x00: // Go to sleep in DOS idle loop if error flag is clear and no command received Idle = !(ram[0x26c] | ram[0x7c]); jump(0xebff); break; case 0x01: // Write sector the_job->WriteSector(); jump(0xf5dc); break; case 0x02: // Format track the_job->FormatTrack(); jump(0xfd8b); break; default: #if PC_IS_POINTER illegal_op(0xf2, pc-pc_base-1); #else illegal_op(0xf2, pc-1); #endif break; } break; } } return last_cycles; }