From d1df9b940dcc068b3c1257398c3d525e57b208e1 Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Mon, 19 Aug 2019 13:42:32 -0400 Subject: [PATCH] c64? maybe --- src/platform/c64.ts | 593 +++++++++++++++++++++++++++++++++++++++ src/ui.ts | 2 +- src/views.ts | 2 +- src/worker/workermain.ts | 23 +- 4 files changed, 613 insertions(+), 7 deletions(-) create mode 100644 src/platform/c64.ts diff --git a/src/platform/c64.ts b/src/platform/c64.ts new file mode 100644 index 00000000..cc721294 --- /dev/null +++ b/src/platform/c64.ts @@ -0,0 +1,593 @@ +"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 + +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; + +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 + +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 + 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= 0) { diff --git a/src/views.ts b/src/views.ts index 17e5a3df..d9b101de 100644 --- a/src/views.ts +++ b/src/views.ts @@ -550,7 +550,7 @@ export class MemoryView implements ProjectView { w: $(workspace).width(), h: $(workspace).height(), itemHeight: getVisibleEditorLineHeight(), - totalRows: 0x2000, + totalRows: 0x1400, generatorFn: (row : number) => { var s = this.getMemoryLineAt(row); var linediv = document.createElement("div"); diff --git a/src/worker/workermain.ts b/src/worker/workermain.ts index cd10a11a..91fba4bd 100644 --- a/src/worker/workermain.ts +++ b/src/worker/workermain.ts @@ -271,11 +271,6 @@ var PLATFORM_PARAMS = { libargs: ['atari5200.lib', '-D', '__CARTFLAGS__=255'], }, - 'c64': { - define: '__C64__', - cfgfile: 'c64.cfg', - libargs: ['c64.lib'], - }, 'verilog': { }, 'astrocade': { @@ -321,6 +316,24 @@ var PLATFORM_PARAMS = { {name:'Cartridge ROM',start:0x4000,size:0xc000,type:'rom'}, ], }, + 'c64': { + define: '__C64__', + cfgfile: 'c64.cfg', // SYS 2058 + libargs: ['c64.lib'], + //extra_link_files: ['c64-cart.cfg'], + extra_segments:[ + {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'}, + {name:'BASIC ROM', start:0xa000,size:0x2000,type:'rom'}, + {name:'RAM', start:0xc000,size:0x1000,type:'ram'}, + {name:'VIC-II I/O', start:0xd000,size:0x0400,type:'io'}, + {name:'Color RAM', start:0xd800,size:0x0400,type:'io'}, + {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'}, + ], + }, }; PLATFORM_PARAMS['sms-sms-libcv'] = PLATFORM_PARAMS['sms-sg1000-libcv'];