diff --git a/src/machine/c64.ts b/src/machine/c64.ts new file mode 100644 index 00000000..776e6d23 --- /dev/null +++ b/src/machine/c64.ts @@ -0,0 +1,563 @@ + +import { MOS6502, MOS6502State } from "../cpu/MOS6502"; +import { BasicMachine, RasterFrameBased, Bus, ProbeAll } from "../devices"; +import { KeyFlags, newAddressDecoder, padBytes, Keys, makeKeycodeMap, newKeyboardHandler, EmuHalt, dumpRAM } from "../emu"; +import { lzgmini, stringToByteArray, hex, rgb2bgr } from "../util"; + +// https://www.c64-wiki.com/wiki/C64 +// http://www.zimmers.net/cbmpics/cbm/c64/vic-ii.txt +// http://www.zimmers.net/cbmpics/cbm/c64/c64prg.txt +// http://sta.c64.org/cbm64mem.html +// http://hitmen.c02.at/temp/palstuff/ + +const KEYBOARD_ROW_0 = 0; +const SWCHA = 8; +const SWCHB = 9; +const INPT0 = 10; + +const C64_KEYCODE_MAP = makeKeycodeMap([ + [Keys.A, INPT0+0, 0x80], + [Keys.B, INPT0+1, 0x80], + [Keys.SELECT, SWCHB, -0x02], + [Keys.START, SWCHB, -0x01], + [Keys.UP, SWCHA, -0x10], + [Keys.DOWN, SWCHA, -0x20], + [Keys.LEFT, SWCHA, -0x40], + [Keys.RIGHT, SWCHA, -0x80], + + [Keys.P2_A, INPT0+2, 0x80], + [Keys.P2_B, INPT0+3, 0x80], + //[Keys.P2_SELECT, 1, 2], + //[Keys.P2_START, 1, 3], + [Keys.P2_UP, SWCHA, -0x01], + [Keys.P2_DOWN, SWCHA, -0x02], + [Keys.P2_LEFT, SWCHA, -0x04], + [Keys.P2_RIGHT, SWCHA, -0x08], +]); + +const C64_KEYMATRIX_NOSHIFT = [ + Keys.VK_DELETE, Keys.VK_ENTER, Keys.VK_RIGHT, Keys.VK_F7, Keys.VK_F1, Keys.VK_F3, Keys.VK_F5, Keys.VK_DOWN, + Keys.VK_3, Keys.VK_W, Keys.VK_A, Keys.VK_4, Keys.VK_Z, Keys.VK_S, Keys.VK_E, Keys.VK_SHIFT, + Keys.VK_5, Keys.VK_R, Keys.VK_D, Keys.VK_6, Keys.VK_C, Keys.VK_F, Keys.VK_T, Keys.VK_X, + Keys.VK_7, Keys.VK_Y, Keys.VK_G, Keys.VK_8, Keys.VK_B, Keys.VK_H, Keys.VK_U, Keys.VK_V, + Keys.VK_9, Keys.VK_I, Keys.VK_J, Keys.VK_0, Keys.VK_M, Keys.VK_K, Keys.VK_O, Keys.VK_N, + null/*Keys.VK_PLUS*/, Keys.VK_P, Keys.VK_L, Keys.VK_MINUS, Keys.VK_PERIOD, null/*Keys.VK_COLON*/, null/*Keys.VK_AT*/, Keys.VK_COMMA, + null/*Keys.VK_POUND*/, null/*TIMES*/, Keys.VK_SEMICOLON, Keys.VK_HOME, Keys.VK_SHIFT/*right*/, Keys.VK_EQUALS, Keys.VK_TILDE, Keys.VK_SLASH, + Keys.VK_1, Keys.VK_LEFT, Keys.VK_CONTROL, Keys.VK_2, Keys.VK_SPACE, Keys.VK_ALT, Keys.VK_Q, null/*STOP*/, +]; + +// CIA +// TODO: https://www.c64-wiki.com/wiki/CIA + +class CIA { + regs = new Uint8Array(0x10); + + reset() { + this.regs.fill(0); + } + read(a : number) : number { + return this.regs[a] | 0; + } + write(a : number, v : number) { + this.regs[a] = v; + } +} + +// VIC-II chip + +class VIC_II { + platform : C64; + scanline : number = 0; + regs = new Uint8Array(0x40); // 64 bytes + cram = new Uint8Array(0x400); // color RAM + pixels = new Uint8Array(numVisiblePixels); // output pixel buffer + cycle : number = 0; + vc : number = 0; + vcbase : number = 0; + rc : number = 0; + vmli : number = 0; // TODO: we don't use + + constructor(platform) { + this.platform = platform; + } + reset() { + this.regs.fill(0); + this.cram.fill(0); + this.vc = 0; + this.vcbase = 0; + this.rc = 0; + this.vmli = 0; + } + read(a : number) : number { + switch (a) { + case 0x12: return this.scanline; + } + return this.regs[a] | 0; + } + write(a : number, v : number) { + this.regs[a] = v; + } + setScanline(sl : number) { + this.scanline = sl; + // interrupt? + if (sl == (this.regs[0x12] | ((this.regs[0x11]&0x80)<<1))) { + this.regs[0x19] |= 0x81; + } + // reset pixel clock + this.cycle = 0; + // reset VCBASE + if (sl < firstScanline) this.vcbase = 0; + // clear pixel buffer w/ background + this.pixels.fill(this.regs[0x21]); + } + saveState() { + return { + regs: this.regs.slice(0), + cram: this.cram.slice(0) + }; + } + loadState(s) { + for (let i=0; i<32; i++) + this.write(i, s.regs[i]); + this.cram.set(s.cram); + } + c_access() : number { + let vm = this.regs[0x18]; + let vadr = (this.vc & 0x3ff) | ((vm & 0xf0) << 6); + //this.platform.profiler && this.platform.profiler.logRead(vadr); + return this.platform.readVRAMAddress(vadr) | (this.cram[vadr & 0x3ff] << 8); + } + g_access(data : number) : number { + let cb = this.regs[0x18]; + let vadr = (this.rc & 7) | ((data & 0xff) << 3) | ((cb & 0xe) << 10); + this.vc = (this.vc + 1) & 0x3ff; + //this.vcbase = (this.vcbase + 1) & 0x3ff; + //this.platform.profiler && this.platform.profiler.logRead(vadr); + return this.platform.readVRAMAddress(vadr); + } + getx() : number { + // TODO: left border 0x1f, 0x18 + return (this.cycle - 16)*8 + numBorderPixels; + } + clockPulse() { + switch (this.cycle) { + case 14: + this.vc = this.vcbase; + //console.log("VC ->",hex(this.vc)); + this.vmli = 0; + break; + case 58: + if (this.rc == 7) { + this.vcbase = this.vc; + //console.log("VCBASE",hex(this.vc)); + // TODO + } + this.rc = (this.rc + 1) & 7; + break; + } + let x = this.getx(); + if (this.cycle >= 16 && this.cycle < 56) { + var cdata = this.c_access(); + let gdata = this.g_access(cdata); + let fgcol = cdata >> 8; + let bgcol = this.regs[0x21]; + for (let i=0; i<8; i++) { + this.pixels[x+i] = (gdata & 0x80) ? fgcol : bgcol; + gdata <<= 1; + } + } + this.cycle++; + } + doDMA() { + // TODO + //let bus = this.platform.bus; + //let profiler = this.platform.profiler; + if (true) { //this.isDMAEnabled()) { + } + return 0; //TODO + } + doInterrupt() : boolean { + return false; // TODO + } + static stateToLongString(state) : string { + let s = ""; + s += dumpRAM(state.regs, 0, 64); + //s += "\nScanline: " + state.scanline; + //s += "\n DLL: $" + hex((state.regs[0x0c] << 8) + state.regs[0x10],4) + " @ $" + hex(state.dll,4); + return s; + } +} + +const cpuFrequency = 10227273; // NTSC +const linesPerFrame = 263; // (6567R8) +const firstScanline = 0x30; +const lastScanline = 0xf7; +const numVisibleLines = 235; // (6567R8) +const numScreenPixels = 320; +const numBorderPixels = 16; +const numVisiblePixels = numScreenPixels+numBorderPixels*2; // (6567R8) +const cpuClocksPerLine = 65; // 65*8 (6567R8) +const cpuClocksPreDMA = 7; // TODO +const audioOversample = 4; +const audioSampleRate = linesPerFrame*60*audioOversample; + +export class C64 extends BasicMachine implements RasterFrameBased { + + cpuFrequency = cpuFrequency; + canvasWidth = numVisiblePixels; + overscan = true; + numTotalScanlines = linesPerFrame; + numVisibleScanlines = numVisibleLines; + defaultROMSize = 0x6000; + cpuCyclesPerLine = 65; // 65*8 (6567R8) + cpuCyclesPreDMA = 7; // TODO + sampleRate = audioSampleRate; + + cpu : MOS6502; + ram : Uint8Array; + rom : Uint8Array; // cartridge ROM + bios : Uint8Array; + vic : VIC_II = new VIC_II(this); + cia1 : CIA = new CIA(); + cia2 : CIA = new CIA(); + probeDMABus; + lastFrameCycles : number = 0; + + enableKERNAL : boolean = true; + enableIO : boolean = true; + enableBASIC : boolean = true; + enableCART : boolean = true; + + constructor() { + super(); + this.cpu = new MOS6502(); + this.ram = new Uint8Array(0x10000); // 64 KB, of course + this.bios = new lzgmini().decode(stringToByteArray(atob(C64_BIOS_LZG))); // BASIC-CHAR-KERNAL ROMs + this.connectCPUMemoryBus(this); + this.probeDMABus = this.probeIOBus(this); + this.handler = newKeyboardHandler(this.inputs, C64_KEYCODE_MAP, this.getKeyboardFunction(), true); + } + + read = newAddressDecoder([ + [0x8000, 0x9fff, 0x1fff, (a) => { return this.enableCART ? (this.rom&&this.rom[a]) : this.ram[a|0x8000]; }], // CART ROM + [0xa000, 0xbfff, 0x1fff, (a) => { return this.enableBASIC ? this.bios[a] : this.ram[a|0xa000]; }], // BASIC ROM + [0xd000, 0xdfff, 0xfff, (a) => { return !this.enableIO ? this.bios[a + 0x2000] : this.readIO(a) }], // CHAR ROM + [0xe000, 0xffff, 0x1fff, (a) => { return this.enableKERNAL ? this.bios[a + 0x3000] : this.ram[a|0xe000]; }], // KERNAL ROM + [0x0000, 0xffff, 0xffff, (a) => { return this.ram[a]; }], + ]); + + write = newAddressDecoder([ + [0x0000, 0x0001, 0xffff, (a,v) => { this.write6510(a,v); }], + [0xd000, 0xdfff, 0xfff, (a,v) => { this.writeIO(a,v); }], + [0x0000, 0xffff, 0xffff, (a,v) => { this.ram[a] = v; }], + ]); + + getKeyboardMap() { return null; /* TODO: C64_KEYCODE_MAP;*/ } + + // http://map.grauw.nl/articles/keymatrix.php + // https://codebase64.org/doku.php?id=base:reading_the_keyboard + // http://www.c64os.com/post?p=45 + // https://www.c64-wiki.com/wiki/Keyboard + + getKeyboardFunction() { + return (o,key,code,flags) => { + //console.log(o,key,code,flags); + var keymap = C64_KEYMATRIX_NOSHIFT; + for (var i=0; i> 3; + let col = i & 7; + // is column selected? + if (flags & KeyFlags.KeyDown) { + this.inputs[KEYBOARD_ROW_0 + row] |= (1<>8); + switch (page) { + case 0x0: case 0x1: case 0x2: case 0x3: + this.vic.write(a & 0x3f, v); + break; + case 0x8: case 0x9: case 0xa: case 0xb: + this.vic.cram[a & 0x3ff] = v; + break; + case 0xc: + this.cia1.write(a & 0xf, v); + break; + case 0xd: + this.cia2.write(a & 0xf, v); + break; + default: + return; //TODO + } + } + + readIO(a:number) : number { + //this.profiler && this.profiler.logRead(a+0xd000); + var page = (a>>8); + switch (page) { + case 0x0: case 0x1: case 0x2: case 0x3: // VIC-II + return this.vic.read(a & 0x3f); + case 0x8: case 0x9: case 0xa: case 0xb: + return this.vic.cram[a & 0x3ff]; + case 0xc: + switch (a & 0xf) { + // scan keyboard matrix for CIA 1 + // CIA 1 regs: [00, 00, ff, 00] or [ff, 00, ff, 00] + // http://www.c64os.com/post?p=45 + case 0x1: + let cols = 0; + for (let i=0; i<8; i++) + if ((this.cia1.regs[0] & (1<= 0x1000 && a < 0x2000) && (bank == 0 || bank == 2)) return this.bios[0x1000 + a]; // CHAR ROM + else return this.ram[a]; + } + + advanceFrame(maxClocks, trap) : number { + var idata = this.pixels; + var iofs = 0; + var vicClocks = cpuClocksPreDMA; + var fc = 0; + this.probe.logNewFrame(); + // visible lines + for (var sl=0; sl= firstScanline && sl <= lastScanline; + // iterate CPU with free clocks + while (vicClocks > 0) { + // next CPU clock + vicClocks--; + if (trap && trap()) { + trap = null; + sl = 999; + break; + } + if (visible) this.vic.clockPulse(); // VIC first + this.advanceCPU(); + fc++; + } + vicClocks += cpuClocksPerLine; + if (visible) { + // do DMA for scanline? + vicClocks -= this.vic.doDMA(); + } + // copy line to frame buffer + if (idata && sl > firstScanline-24 && iofs < idata.length) { + for (var i=0; i> 8; + } else { + // assume cartridge ROM + this.rom = padBytes(data, this.defaultROMSize); + } + this.reset(); + } + + // BASIC (0x2000 bytes) + // CHAR (0x1000 bytes) + // KERNAL (0x2000 bytes) + loadBIOS(data) { + this.bios = padBytes(data, 0x5000); + this.reset(); + } + + reset() { + super.reset(); + this.vic.reset(); + this.cia1.reset(); + this.cia2.reset(); + this.write6510(0, 0xff); + this.inputs.fill(0x0); + } + + // TODO: don't log if profiler active + readConst(addr : number) { + return this.read(addr) | 0; + } + + loadState(state) { + this.cpu.loadState(state.c); + this.ram.set(state.ram); + this.vic.loadState(state.vic); + this.loadControlsState(state); + } + saveState() { + return { + c:this.getCPUState(), + ram:this.ram.slice(0), + vic:this.vic.saveState(), + inputs:this.inputs.slice(0) + }; + } + + loadControlsState(state) { + this.inputs.set(state.inputs); + } + + saveControlsState() { + return { + inputs:this.inputs.slice(0) + }; + } + + getCPUState() { + return this.cpu.saveState(); + } + + getRasterScanline() { + return this.vic.scanline; + } + + getDebugCategories() { + return ['VIC-II']; + } + getDebugInfo(category, state) { + switch (category) { + case 'VIC-II': return VIC_II.stateToLongString(state.vic); + } + } + +} + + +const VIC_NTSC_RGB = [ + 0x000000, + 0xFFFFFF, + 0x880000, + 0xAAFFEE, + 0xCC44CC, + 0x00CC55, + 0x00CC55, + 0xEEEE77, + 0xDD8855, + 0x664400, + 0xFF7777, + 0x333333, + 0x777777, + 0xAAFF66, + 0x0088FF, + 0xBBBBBB, +]; + +var COLORS_RGBA = new Uint32Array(256); +var COLORS_WEB = []; +for (var i=0; i<256; i++) { + COLORS_RGBA[i] = rgb2bgr(VIC_NTSC_RGB[i & 15]) | 0xff000000; + COLORS_WEB[i] = "#"+hex(VIC_NTSC_RGB[i & 15],6); +} + +// bank-switching table +// TODO: https://www.c64-wiki.com/wiki/Bank_Switching + +enum BankSwitchFlags { + LORAM=1, HIRAM=2, CHAREN=4, _GAME=8, _EXROM=16 +}; + +enum Bank { + NONE, RAM, IO, CART_ROM_LO, CART_ROM_HI, CHAR_ROM, KERN_ROM, BASIC_ROM +}; + +function getBankMapping(flags:number, region:number) : Bank { + return BANK_TABLE[flags & 0x1f][region]; +}; + +const BANK_TABLE : Bank[][] = [ + [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM ], + [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM ], + [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.CART_ROM_HI, Bank.RAM, Bank.CHAR_ROM, Bank.KERN_ROM ], + [ Bank.RAM, Bank.RAM, Bank.CART_ROM_LO, Bank.CART_ROM_HI, Bank.RAM, Bank.CHAR_ROM, Bank.KERN_ROM ], + [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM ], + [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.IO, Bank.RAM ], + [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.CART_ROM_HI, Bank.RAM, Bank.IO, Bank.KERN_ROM ], + [ Bank.RAM, Bank.RAM, Bank.CART_ROM_LO, Bank.CART_ROM_HI, Bank.RAM, Bank.IO, Bank.KERN_ROM ], + [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM ], + [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.CHAR_ROM, Bank.RAM ], + [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.CHAR_ROM, Bank.KERN_ROM ], + [ Bank.RAM, Bank.RAM, Bank.CART_ROM_LO, Bank.BASIC_ROM, Bank.RAM, Bank.CHAR_ROM, Bank.KERN_ROM ], + [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM ], + [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.IO, Bank.RAM ], + [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.IO, Bank.KERN_ROM ], + [ Bank.RAM, Bank.RAM, Bank.CART_ROM_LO, Bank.BASIC_ROM, Bank.RAM, Bank.IO, Bank.KERN_ROM ], + [ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ], + [ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ], + [ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ], + [ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ], + [ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ], + [ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ], + [ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ], + [ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ], + [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM ], + [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.CHAR_ROM, Bank.RAM ], + [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.CHAR_ROM, Bank.KERN_ROM ], + [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.BASIC_ROM, Bank.RAM, Bank.CHAR_ROM, Bank.KERN_ROM ], + [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM ], + [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.IO, Bank.RAM ], + [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.IO, Bank.KERN_ROM ], + [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.BASIC_ROM, Bank.RAM, Bank.IO, Bank.KERN_ROM ], +]; + +// https://github.com/MEGA65/open-roms +var C64_BIOS_LZG = `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`; diff --git a/src/platform/atari7800.ts b/src/platform/atari7800.ts index 1ba7f34d..023c4e54 100644 --- a/src/platform/atari7800.ts +++ b/src/platform/atari7800.ts @@ -1,5 +1,4 @@ -import { MOS6502 } from "../cpu/MOS6502"; import { Atari7800 } from "../machine/atari7800"; import { Platform, Base6502MachinePlatform } from "../baseplatform"; import { PLATFORMS } from "../emu"; diff --git a/src/platform/c64.ts b/src/platform/c64.ts index 7875de04..e4a66fef 100644 --- a/src/platform/c64.ts +++ b/src/platform/c64.ts @@ -1,529 +1,20 @@ -"use strict"; -import { Platform, Base6502Platform, BaseMAMEPlatform, getOpcodeMetadata_6502, getToolForFilename_6502 } from "../baseplatform"; -import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap, dumpRAM, getMousePos, EmuHalt, KeyFlags } from "../emu"; -import { hex, lzgmini, stringToByteArray, lpad, rpad, rgb2bgr } from "../util"; -import { MasterAudio, POKEYDeviceChannel, newPOKEYAudio } from "../audio"; - -declare var jt; // for 6502 - -// https://www.c64-wiki.com/wiki/C64 -// http://www.zimmers.net/cbmpics/cbm/c64/vic-ii.txt -// http://www.zimmers.net/cbmpics/cbm/c64/c64prg.txt -// http://sta.c64.org/cbm64mem.html -// http://hitmen.c02.at/temp/palstuff/ +import { C64 } from "../machine/c64"; +import { Platform, Base6502MachinePlatform } from "../baseplatform"; +import { PLATFORMS } from "../emu"; var C64_PRESETS = [ {id:'hello.dasm', name:'Hello World (ASM)'}, ]; -// TODO -const KEYBOARD_ROW_0 = 0; -const SWCHA = 8; -const SWCHB = 9; -const INPT0 = 10; +class C64Platform extends Base6502MachinePlatform implements Platform { -const C64_KEYCODE_MAP = makeKeycodeMap([ - [Keys.A, INPT0+0, 0x80], - [Keys.B, INPT0+1, 0x80], - [Keys.SELECT, SWCHB, -0x02], - [Keys.START, SWCHB, -0x01], - [Keys.UP, SWCHA, -0x10], - [Keys.DOWN, SWCHA, -0x20], - [Keys.LEFT, SWCHA, -0x40], - [Keys.RIGHT, SWCHA, -0x80], - - [Keys.P2_A, INPT0+2, 0x80], - [Keys.P2_B, INPT0+3, 0x80], - //[Keys.P2_SELECT, 1, 2], - //[Keys.P2_START, 1, 3], - [Keys.P2_UP, SWCHA, -0x01], - [Keys.P2_DOWN, SWCHA, -0x02], - [Keys.P2_LEFT, SWCHA, -0x04], - [Keys.P2_RIGHT, SWCHA, -0x08], -]); - -const cpuFrequency = 10227273; // NTSC -const linesPerFrame = 263; // (6567R8) -const firstScanline = 0x30; -const lastScanline = 0xf7; -const numVisibleLines = 235; // (6567R8) -const numScreenPixels = 320; -const numBorderPixels = 16; -const numVisiblePixels = numScreenPixels+numBorderPixels*2; // (6567R8) -const cpuClocksPerLine = 65; // 65*8 (6567R8) -const cpuClocksPreDMA = 7; // TODO -const romLength = 0x6000; - -const C64_KEYMATRIX_NOSHIFT = [ - Keys.VK_DELETE, Keys.VK_ENTER, Keys.VK_RIGHT, Keys.VK_F7, Keys.VK_F1, Keys.VK_F3, Keys.VK_F5, Keys.VK_DOWN, - Keys.VK_3, Keys.VK_W, Keys.VK_A, Keys.VK_4, Keys.VK_Z, Keys.VK_S, Keys.VK_E, Keys.VK_SHIFT, - Keys.VK_5, Keys.VK_R, Keys.VK_D, Keys.VK_6, Keys.VK_C, Keys.VK_F, Keys.VK_T, Keys.VK_X, - Keys.VK_7, Keys.VK_Y, Keys.VK_G, Keys.VK_8, Keys.VK_B, Keys.VK_H, Keys.VK_U, Keys.VK_V, - Keys.VK_9, Keys.VK_I, Keys.VK_J, Keys.VK_0, Keys.VK_M, Keys.VK_K, Keys.VK_O, Keys.VK_N, - null/*Keys.VK_PLUS*/, Keys.VK_P, Keys.VK_L, Keys.VK_MINUS, Keys.VK_PERIOD, null/*Keys.VK_COLON*/, null/*Keys.VK_AT*/, Keys.VK_COMMA, - null/*Keys.VK_POUND*/, null/*TIMES*/, Keys.VK_SEMICOLON, Keys.VK_HOME, Keys.VK_SHIFT/*right*/, Keys.VK_EQUALS, Keys.VK_TILDE, Keys.VK_SLASH, - Keys.VK_1, Keys.VK_LEFT, Keys.VK_CONTROL, Keys.VK_2, Keys.VK_SPACE, Keys.VK_ALT, Keys.VK_Q, null/*STOP*/, -]; - -// CIA -// TODO: https://www.c64-wiki.com/wiki/CIA - -class CIA { - regs = new Uint8Array(0x10); - - reset() { - this.regs.fill(0); - } - read(a : number) : number { - return this.regs[a] | 0; - } - write(a : number, v : number) { - this.regs[a] = v; - } -} - -// VIC-II chip - -class VIC_II { - platform : C64Platform; - scanline : number = 0; - regs = new Uint8Array(0x40); // 64 bytes - cram = new Uint8Array(0x400); // color RAM - pixels = new Uint8Array(numVisiblePixels); // output pixel buffer - cycle : number = 0; - vc : number = 0; - vcbase : number = 0; - rc : number = 0; - vmli : number = 0; // TODO: we don't use - - constructor(platform) { - this.platform = platform; - } - reset() { - this.regs.fill(0); - this.cram.fill(0); - this.vc = 0; - this.vcbase = 0; - this.rc = 0; - this.vmli = 0; - } - read(a : number) : number { - switch (a) { - case 0x12: return this.scanline; - } - return this.regs[a] | 0; - } - write(a : number, v : number) { - this.regs[a] = v; - } - setScanline(sl : number) { - this.scanline = sl; - // interrupt? - if (sl == (this.regs[0x12] | ((this.regs[0x11]&0x80)<<1))) { - this.regs[0x19] |= 0x81; - } - // reset pixel clock - this.cycle = 0; - // reset VCBASE - if (sl < firstScanline) this.vcbase = 0; - // clear pixel buffer w/ background - this.pixels.fill(this.regs[0x21]); - } - saveState() { - return { - regs: this.regs.slice(0), - cram: this.cram.slice(0) - }; - } - loadState(s) { - for (let i=0; i<32; i++) - this.write(i, s.regs[i]); - this.cram.set(s.cram); - } - c_access() : number { - let vm = this.regs[0x18]; - let vadr = (this.vc & 0x3ff) | ((vm & 0xf0) << 6); - //this.platform.profiler && this.platform.profiler.logRead(vadr); - return this.platform.readVRAMAddress(vadr) | (this.cram[vadr & 0x3ff] << 8); - } - g_access(data : number) : number { - let cb = this.regs[0x18]; - let vadr = (this.rc & 7) | ((data & 0xff) << 3) | ((cb & 0xe) << 10); - this.vc = (this.vc + 1) & 0x3ff; - //this.vcbase = (this.vcbase + 1) & 0x3ff; - //this.platform.profiler && this.platform.profiler.logRead(vadr); - return this.platform.readVRAMAddress(vadr); - } - getx() : number { - // TODO: left border 0x1f, 0x18 - return (this.cycle - 16)*8 + numBorderPixels; - } - clockPulse() { - switch (this.cycle) { - case 14: - this.vc = this.vcbase; - //console.log("VC ->",hex(this.vc)); - this.vmli = 0; - break; - case 58: - if (this.rc == 7) { - this.vcbase = this.vc; - //console.log("VCBASE",hex(this.vc)); - // TODO - } - this.rc = (this.rc + 1) & 7; - break; - } - let x = this.getx(); - if (this.cycle >= 16 && this.cycle < 56) { - var cdata = this.c_access(); - let gdata = this.g_access(cdata); - let fgcol = cdata >> 8; - let bgcol = this.regs[0x21]; - for (let i=0; i<8; i++) { - this.pixels[x+i] = (gdata & 0x80) ? fgcol : bgcol; - gdata <<= 1; - } - } - this.cycle++; - } - doDMA(platform : C64Platform) { - // TODO - let bus = this.platform.bus; - let profiler = this.platform.profiler; - if (true) { //this.isDMAEnabled()) { - } - return 0; //TODO - } - doInterrupt() : boolean { - return false; // TODO - } - static stateToLongString(state) : string { - let s = ""; - s += dumpRAM(state.regs, 0, 64); - //s += "\nScanline: " + state.scanline; - //s += "\n DLL: $" + hex((state.regs[0x0c] << 8) + state.regs[0x10],4) + " @ $" + hex(state.dll,4); - return s; - } -} - -// Atari 7800 - -class C64Platform extends Base6502Platform implements Platform { - - mainElement : HTMLElement; - cpu; - ram : Uint8Array; - rom : Uint8Array; // cartridge ROM - bios : Uint8Array; - bus; - video; - audio; - timer : AnimationTimer; - inputs = new Uint8Array(16); - vic : VIC_II = new VIC_II(this); - cia1 : CIA = new CIA(); - cia2 : CIA = new CIA(); - - enableKERNAL : boolean = true; - enableIO : boolean = true; - enableBASIC : boolean = true; - enableCART : boolean = true; - - constructor(mainElement : HTMLElement) { - super(); - this.mainElement = mainElement; - } - - getPresets() { - return C64_PRESETS; - } - - getKeyboardMap() { return null; /* TODO: C64_KEYCODE_MAP;*/ } - - // http://map.grauw.nl/articles/keymatrix.php - // https://codebase64.org/doku.php?id=base:reading_the_keyboard - // http://www.c64os.com/post?p=45 - // https://www.c64-wiki.com/wiki/Keyboard - getKeyboardFunction() { - return (o,key,code,flags) => { - //console.log(o,key,code,flags); - var keymap = C64_KEYMATRIX_NOSHIFT; - for (var i=0; i> 3; - let col = i & 7; - // is column selected? - if (flags & KeyFlags.KeyDown) { - this.inputs[KEYBOARD_ROW_0 + row] |= (1<>8); - switch (page) { - case 0x0: case 0x1: case 0x2: case 0x3: - this.vic.write(a & 0x3f, v); - break; - case 0x8: case 0x9: case 0xa: case 0xb: - this.vic.cram[a & 0x3ff] = v; - break; - case 0xc: - this.cia1.write(a & 0xf, v); - break; - case 0xd: - this.cia2.write(a & 0xf, v); - break; - default: - return; //TODO - } - } - - readIO(a:number) : number { - this.profiler && this.profiler.logRead(a+0xd000); - var page = (a>>8); - switch (page) { - case 0x0: case 0x1: case 0x2: case 0x3: // VIC-II - return this.vic.read(a & 0x3f); - case 0x8: case 0x9: case 0xa: case 0xb: - return this.vic.cram[a & 0x3ff]; - case 0xc: - switch (a & 0xf) { - // scan keyboard matrix for CIA 1 - // CIA 1 regs: [00, 00, ff, 00] or [ff, 00, ff, 00] - // http://www.c64os.com/post?p=45 - case 0x1: - let cols = 0; - for (let i=0; i<8; i++) - if ((this.cia1.regs[0] & (1<= 0x1000 && a < 0x2000) && (bank == 0 || bank == 2)) return this.bios[0x1000 + a]; // CHAR ROM - else return this.ram[a]; - } - - start() { - this.cpu = new jt.M6502(); - this.ram = new Uint8Array(0x10000); // 64 KB, of course - this.bios = new lzgmini().decode(stringToByteArray(atob(C64_BIOS_LZG))); // BASIC-CHAR-KERNAL ROMs - // TODO: https://www.c64-wiki.com/wiki/Bank_Switching - this.bus = { - read: newAddressDecoder([ - [0x8000, 0x9fff, 0x1fff, (a) => { return this.enableCART ? (this.rom&&this.rom[a]) : this.ram[a|0x8000]; }], // CART ROM - [0xa000, 0xbfff, 0x1fff, (a) => { return this.enableBASIC ? this.bios[a] : this.ram[a|0xa000]; }], // BASIC ROM - [0xd000, 0xdfff, 0xfff, (a) => { return !this.enableIO ? this.bios[a + 0x2000] : this.readIO(a) }], // CHAR ROM - [0xe000, 0xffff, 0x1fff, (a) => { return this.enableKERNAL ? this.bios[a + 0x3000] : this.ram[a|0xe000]; }], // KERNAL ROM - [0x0000, 0xffff, 0xffff, (a) => { return this.ram[a]; }], - ]), - write: newAddressDecoder([ - [0x0000, 0x0001, 0xffff, (a,v) => { this.write6510(a,v); }], - [0xd000, 0xdfff, 0xfff, (a,v) => { this.writeIO(a,v); }], - [0x0000, 0xffff, 0xffff, (a,v) => { this.ram[a] = v; }], - ]), - }; - this.cpu.connectBus(this.bus); - // create video/audio - this.video = new RasterVideo(this.mainElement, numVisiblePixels, numVisibleLines, {overscan:true}); - this.audio = newPOKEYAudio(1); - this.video.create(); - setKeyboardFromMap(this.video, this.inputs, this.getKeyboardMap(), this.getKeyboardFunction()); - this.timer = new AnimationTimer(60, this.nextFrame.bind(this)); - // setup mouse events - var rasterPosBreakFn = (e) => { - if (e.shiftKey) { - var clickpos = getMousePos(e.target, e); - this.runEval( (c) => { - return (this.getRasterScanline() == (clickpos.y|0)); - }); - } - }; - var jacanvas = $("#emulator").find("canvas"); - jacanvas.mousedown(rasterPosBreakFn); - } - - advance(novideo : boolean) { - var idata = this.video.getFrameData(); - var iofs = 0; - var debugCond = this.getDebugCallback(); - var rgb; - var vicClocks = cpuClocksPreDMA; - // visible lines - for (var sl=0; sl= firstScanline && sl <= lastScanline; - // iterate CPU with free clocks - while (vicClocks > 0) { - // next CPU clock - vicClocks--; - if (debugCond && debugCond()) { - debugCond = null; - sl = 999; - break; - } - if (visible) this.vic.clockPulse(); // VIC first - this.cpu.clockPulse(); - } - vicClocks += cpuClocksPerLine; - if (visible) { - // do DMA for scanline? - vicClocks -= this.vic.doDMA(this); - } - // copy line to frame buffer - if (sl > firstScanline-24 && iofs < idata.length) { - for (var i=0; i> 8; - } else { - // assume cartridge ROM - this.rom = padBytes(data, romLength); - } - this.reset(); - } - - // BASIC (0x2000 bytes) - // CHAR (0x1000 bytes) - // KERNAL (0x2000 bytes) - loadBIOS(title, data) { - this.bios = padBytes(data, 0x5000); - this.reset(); - } - - isRunning() { - return this.timer.isRunning(); - } - - pause() { - this.timer.stop(); - this.audio.stop(); - } - - resume() { - this.timer.start(); - this.audio.start(); - } - - reset() { - this.cpu.reset(); - this.vic.reset(); - this.cia1.reset(); - this.cia2.reset(); - this.write6510(0, 0xff); - this.inputs.fill(0x0); - this.cpu.clockPulse(); // TODO: needed for test to pass? - } - - // TODO: don't log if profiler active - readAddress(addr : number) { - return this.bus.read(addr) | 0; - } - - loadState(state) { - this.unfixPC(state.c); - this.cpu.loadState(state.c); - this.fixPC(state.c); - this.ram.set(state.b); - this.vic.loadState(state.vic); - this.loadControlsState(state); - } - saveState() { - return { - c:this.getCPUState(), - b:this.ram.slice(0), - vic:this.vic.saveState(), - in:this.inputs.slice(0) - }; - } - - loadControlsState(state) { - this.inputs.set(state.in); - } - - saveControlsState() { - return { - in:this.inputs.slice(0) - }; - } - - getCPUState() { - return this.fixPC(this.cpu.saveState()); - } - - getRasterScanline() { - return this.vic.scanline; - } - - getDebugCategories() { - return super.getDebugCategories().concat(['VIC-II']); - } - getDebugInfo(category, state) { - switch (category) { - case 'VIC-II': return VIC_II.stateToLongString(state.vic); - default: return super.getDebugInfo(category, state); - } - } - - getMemoryMap() { - return { main: [ + newMachine() { return new C64(); } + getPresets() { return C64_PRESETS; } + getDefaultExtension() { return ".c"; }; + readAddress(a) { return this.machine.readConst(a); } + loadBios(bios) { this.machine.loadBIOS(bios); } + getMemoryMap = function() { return { main:[ {name:'6510 Registers',start:0x0, size:0x2,type:'io'}, {name:'RAM', start:0x2, size:0x7ffe,type:'ram'}, {name:'Cartridge ROM',start:0x8000,size:0x2000,type:'rom'}, @@ -534,91 +25,8 @@ class C64Platform extends Base6502Platform implements Platform { {name:'CIA 1', start:0xdc00,size:0x0100,type:'io'}, {name:'CIA 2', start:0xdd00,size:0x0100,type:'io'}, {name:'KERNAL ROM', start:0xe000,size:0x2000,type:'rom'}, - ]}; - } + ] } }; } -/// - -const VIC_NTSC_RGB = [ - 0x000000, - 0xFFFFFF, - 0x880000, - 0xAAFFEE, - 0xCC44CC, - 0x00CC55, - 0x00CC55, - 0xEEEE77, - 0xDD8855, - 0x664400, - 0xFF7777, - 0x333333, - 0x777777, - 0xAAFF66, - 0x0088FF, - 0xBBBBBB, -]; - -var COLORS_RGBA = new Uint32Array(256); -var COLORS_WEB = []; -for (var i=0; i<256; i++) { - COLORS_RGBA[i] = rgb2bgr(VIC_NTSC_RGB[i & 15]) | 0xff000000; - COLORS_WEB[i] = "#"+hex(VIC_NTSC_RGB[i & 15],6); -} - -// bank-switching table -// TODO: https://www.c64-wiki.com/wiki/Bank_Switching - -enum BankSwitchFlags { - LORAM=1, HIRAM=2, CHAREN=4, _GAME=8, _EXROM=16 -}; - -enum Bank { - NONE, RAM, IO, CART_ROM_LO, CART_ROM_HI, CHAR_ROM, KERN_ROM, BASIC_ROM -}; - -function getBankMapping(flags:number, region:number) : Bank { - return BANK_TABLE[flags & 0x1f][region]; -}; - -const BANK_TABLE : Bank[][] = [ - [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM ], - [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM ], - [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.CART_ROM_HI, Bank.RAM, Bank.CHAR_ROM, Bank.KERN_ROM ], - [ Bank.RAM, Bank.RAM, Bank.CART_ROM_LO, Bank.CART_ROM_HI, Bank.RAM, Bank.CHAR_ROM, Bank.KERN_ROM ], - [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM ], - [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.IO, Bank.RAM ], - [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.CART_ROM_HI, Bank.RAM, Bank.IO, Bank.KERN_ROM ], - [ Bank.RAM, Bank.RAM, Bank.CART_ROM_LO, Bank.CART_ROM_HI, Bank.RAM, Bank.IO, Bank.KERN_ROM ], - [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM ], - [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.CHAR_ROM, Bank.RAM ], - [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.CHAR_ROM, Bank.KERN_ROM ], - [ Bank.RAM, Bank.RAM, Bank.CART_ROM_LO, Bank.BASIC_ROM, Bank.RAM, Bank.CHAR_ROM, Bank.KERN_ROM ], - [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM ], - [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.IO, Bank.RAM ], - [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.IO, Bank.KERN_ROM ], - [ Bank.RAM, Bank.RAM, Bank.CART_ROM_LO, Bank.BASIC_ROM, Bank.RAM, Bank.IO, Bank.KERN_ROM ], - [ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ], - [ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ], - [ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ], - [ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ], - [ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ], - [ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ], - [ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ], - [ Bank.RAM, Bank.NONE, Bank.CART_ROM_LO, Bank.NONE, Bank.NONE, Bank.IO, Bank.CART_ROM_HI ], - [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM ], - [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.CHAR_ROM, Bank.RAM ], - [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.CHAR_ROM, Bank.KERN_ROM ], - [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.BASIC_ROM, Bank.RAM, Bank.CHAR_ROM, Bank.KERN_ROM ], - [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM ], - [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.IO, Bank.RAM ], - [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.RAM, Bank.IO, Bank.KERN_ROM ], - [ Bank.RAM, Bank.RAM, Bank.RAM, Bank.BASIC_ROM, Bank.RAM, Bank.IO, Bank.KERN_ROM ], -]; - -/// - PLATFORMS['c64'] = C64Platform; -// https://github.com/MEGA65/open-roms -var C64_BIOS_LZG = `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`;