diff --git a/index.html b/index.html index a48ac908..bf8271c6 100644 --- a/index.html +++ b/index.html @@ -213,7 +213,7 @@ canvas.pixelated { - + diff --git a/src/cpu/z80.coffee b/src/cpu/z80.coffee index 29450709..7b8bb81c 100644 --- a/src/cpu/z80.coffee +++ b/src/cpu/z80.coffee @@ -2314,6 +2314,10 @@ window.buildZ80 = (opts) -> ~48T window, to support retriggered interrupts and interrupt blocking via chains of EI or DD/FD prefixes */ } + self.nonMaskableInterrupt = function() { + iff1 = 1; + self.requestInterrupt(0x66); + } var z80Interrupt = function() { if (iff1) { if (halted) { diff --git a/src/cpu/z80.js b/src/cpu/z80.js index 6cefc455..740c1618 100644 --- a/src/cpu/z80.js +++ b/src/cpu/z80.js @@ -1666,7 +1666,7 @@ of the host processor, as typed arrays are native-endian The indirection on 'eval' causes most browsers to evaluate it in the global scope, giving a significant speed boost */ - defineZ80JS = "window.Z80 = function(opts) {\n var self = {};\n\n " + setUpStateJS + "\n\n self.requestInterrupt = function(dataBus) {\n interruptPending = true;\n interruptDataBus = dataBus & 0xffff;\n /* TODO: use event scheduling to keep the interrupt line active for a fixed\n ~48T window, to support retriggered interrupts and interrupt blocking via\n chains of EI or DD/FD prefixes */\n }\n var z80Interrupt = function() {\n if (iff1) {\n if (halted) {\n /* move PC on from the HALT opcode */\n regPairs[" + rpPC + "]++;\n halted = false;\n }\n\n iff1 = iff2 = 0;\n\n /* push current PC in readiness for call to interrupt handler */\n regPairs[" + rpSP + "]--; WRITEMEM(regPairs[" + rpSP + "], regPairs[" + rpPC + "] >> 8);\n regPairs[" + rpSP + "]--; WRITEMEM(regPairs[" + rpSP + "], regPairs[" + rpPC + "] & 0xff);\n\n /* TODO: R register */\n\n switch (im) {\n case 0:\n regPairs[" + rpPC + "] = interruptDataBus; // assume always RST\n tstates += 6;\n break;\n case 1:\n regPairs[" + rpPC + "] = 0x0038;\n tstates += 7;\n break;\n case 2:\n inttemp = (regs[" + rI + "] << 8) | (interruptDataBus & 0xff);\n l = READMEM(inttemp);\n inttemp = (inttemp+1) & 0xffff;\n h = READMEM(inttemp);\n console.log(hex(interruptDataBus), hex(inttemp), hex(l), hex(h));\n regPairs[" + rpPC + "] = (h<<8) | l;\n tstates += 7;\n break;\n }\n }\n };\n\n self.runFrame = function(frameLength) {\n var lastOpcodePrefix, offset, opcode;\n\n while (tstates < frameLength || opcodePrefix) {\n if (interruptible && interruptPending) {\n z80Interrupt();\n interruptPending = false;\n }\n interruptible = true; /* unless overridden by opcode */\n lastOpcodePrefix = opcodePrefix;\n opcodePrefix = '';\n switch (lastOpcodePrefix) {\n case '':\n CONTEND_READ(regPairs[" + rpPC + "], 4);\n opcode = memory.read(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n regs[" + rR + "] = ((regs[" + rR + "] + 1) & 0x7f) | (regs[" + rR + "] & 0x80);\n " + (opcodeSwitch(OPCODE_RUN_STRINGS, null, opts.traps)) + "\n break;\n case 'CB':\n CONTEND_READ(regPairs[" + rpPC + "], 4);\n opcode = memory.read(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n regs[" + rR + "] = ((regs[" + rR + "] + 1) & 0x7f) | (regs[" + rR + "] & 0x80);\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_CB)) + "\n break;\n case 'DD':\n CONTEND_READ(regPairs[" + rpPC + "], 4);\n opcode = memory.read(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n regs[" + rR + "] = ((regs[" + rR + "] + 1) & 0x7f) | (regs[" + rR + "] & 0x80);\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_DD, OPCODE_RUN_STRINGS)) + "\n break;\n case 'DDCB':\n offset = READMEM(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n if (offset & 0x80) offset -= 0x100;\n CONTEND_READ(regPairs[" + rpPC + "], 3);\n opcode = memory.read(regPairs[" + rpPC + "]);\n CONTEND_READ_NO_MREQ(regPairs[" + rpPC + "], 1);\n CONTEND_READ_NO_MREQ(regPairs[" + rpPC + "], 1);\n regPairs[" + rpPC + "]++;\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_DDCB)) + "\n break;\n case 'ED':\n CONTEND_READ(regPairs[" + rpPC + "], 4);\n opcode = memory.read(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n regs[" + rR + "] = ((regs[" + rR + "] + 1) & 0x7f) | (regs[" + rR + "] & 0x80);\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_ED)) + "\n break;\n case 'FD':\n CONTEND_READ(regPairs[" + rpPC + "], 4);\n opcode = memory.read(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n regs[" + rR + "] = ((regs[" + rR + "] + 1) & 0x7f) | (regs[" + rR + "] & 0x80);\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_FD, OPCODE_RUN_STRINGS)) + "\n break;\n case 'FDCB':\n offset = READMEM(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n if (offset & 0x80) offset -= 0x100;\n CONTEND_READ(regPairs[" + rpPC + "], 3);\n opcode = memory.read(regPairs[" + rpPC + "]);\n CONTEND_READ_NO_MREQ(regPairs[" + rpPC + "], 1);\n CONTEND_READ_NO_MREQ(regPairs[" + rpPC + "], 1);\n regPairs[" + rpPC + "]++;\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_FDCB)) + "\n break;\n default:\n throw(\"Unknown opcode prefix: \" + lastOpcodePrefix);\n }\n }\n while (display.nextEventTime != null && display.nextEventTime <= tstates) display.doEvent();\n };\n\n self.reset = function() {\n regPairs[" + rpPC + "] = regPairs[" + rpIR + "] = 0;\n iff1 = 0; iff2 = 0; im = 0; halted = false;\n };\n\n self.loadState = function(snapRegs) {\n regPairs[" + rpAF + "] = snapRegs['AF'];\n regPairs[" + rpBC + "] = snapRegs['BC'];\n regPairs[" + rpDE + "] = snapRegs['DE'];\n regPairs[" + rpHL + "] = snapRegs['HL'];\n regPairs[" + rpAF_ + "] = snapRegs['AF_'];\n regPairs[" + rpBC_ + "] = snapRegs['BC_'];\n regPairs[" + rpDE_ + "] = snapRegs['DE_'];\n regPairs[" + rpHL_ + "] = snapRegs['HL_'];\n regPairs[" + rpIX + "] = snapRegs['IX'];\n regPairs[" + rpIY + "] = snapRegs['IY'];\n regPairs[" + rpSP + "] = snapRegs['SP'];\n regPairs[" + rpPC + "] = snapRegs['PC'];\n regPairs[" + rpIR + "] = snapRegs['IR'];\n iff1 = snapRegs['iff1'];\n iff2 = snapRegs['iff2'];\n im = snapRegs['im'];\n halted = snapRegs['halted'];\n tstates = snapRegs['tstates'];\n interruptPending = snapRegs['intp'];\n interruptDataBus = snapRegs['intd'];\n };\n\n self.saveState = function() {\n return {\n AF: regPairs[" + rpAF + "],\n BC: regPairs[" + rpBC + "],\n DE: regPairs[" + rpDE + "],\n HL: regPairs[" + rpHL + "],\n AF_: regPairs[" + rpAF_ + "],\n BC_: regPairs[" + rpBC_ + "],\n DE_: regPairs[" + rpDE_ + "],\n HL_: regPairs[" + rpHL_ + "],\n IX: regPairs[" + rpIX + "],\n IY: regPairs[" + rpIY + "],\n SP: regPairs[" + rpSP + "],\n PC: regPairs[" + rpPC + "],\n IR: regPairs[" + rpIR + "],\n iff1: iff1,\n iff2: iff2,\n im: im,\n halted: halted,\n tstates: tstates,\n intp: interruptPending,\n intd: interruptDataBus,\n };\n };\n\n /* Register / flag accessors (used for tape trapping and test harness) */\n self.getAF = function() {\n return regPairs[" + rpAF + "];\n }\n self.getBC = function() {\n return regPairs[" + rpBC + "];\n }\n self.getDE = function() {\n return regPairs[" + rpDE + "];\n }\n self.getHL = function() {\n return regPairs[" + rpHL + "];\n }\n self.getAF_ = function() {\n return regPairs[" + rpAF_ + "];\n }\n self.getBC_ = function() {\n return regPairs[" + rpBC_ + "];\n }\n self.getDE_ = function() {\n return regPairs[" + rpDE_ + "];\n }\n self.getHL_ = function() {\n return regPairs[" + rpHL_ + "];\n }\n self.getIX = function() {\n return regPairs[" + rpIX + "];\n }\n self.getIY = function() {\n return regPairs[" + rpIY + "];\n }\n self.getI = function() {\n return regs[" + rI + "];\n }\n self.getR = function() {\n return regs[" + rR + "];\n }\n self.getSP = function() {\n return regPairs[" + rpSP + "];\n }\n self.getPC = function() {\n return regPairs[" + rpPC + "];\n }\n self.getIFF1 = function() {\n return iff1;\n }\n self.getIFF2 = function() {\n return iff2;\n }\n self.getIM = function() {\n return im;\n }\n self.getHalted = function() {\n return halted;\n }\n\n self.setAF = function(val) {\n regPairs[" + rpAF + "] = val;\n }\n self.setBC = function(val) {\n regPairs[" + rpBC + "] = val;\n }\n self.setDE = function(val) {\n regPairs[" + rpDE + "] = val;\n }\n self.setHL = function(val) {\n regPairs[" + rpHL + "] = val;\n }\n self.setAF_ = function(val) {\n regPairs[" + rpAF_ + "] = val;\n }\n self.setBC_ = function(val) {\n regPairs[" + rpBC_ + "] = val;\n }\n self.setDE_ = function(val) {\n regPairs[" + rpDE_ + "] = val;\n }\n self.setHL_ = function(val) {\n regPairs[" + rpHL_ + "] = val;\n }\n self.setIX = function(val) {\n regPairs[" + rpIX + "] = val;\n }\n self.setIY = function(val) {\n regPairs[" + rpIY + "] = val;\n }\n self.setI = function(val) {\n regs[" + rI + "] = val;\n }\n self.setR = function(val) {\n regs[" + rR + "] = val;\n }\n self.setSP = function(val) {\n regPairs[" + rpSP + "] = val;\n }\n self.setPC = function(val) {\n regPairs[" + rpPC + "] = val;\n }\n self.setIFF1 = function(val) {\n iff1 = val;\n }\n self.setIFF2 = function(val) {\n iff2 = val;\n }\n self.setIM = function(val) {\n im = val;\n }\n self.setHalted = function(val) {\n halted = val;\n }\n\n self.getTstates = function() {\n return tstates;\n }\n self.setTstates = function(val) {\n tstates = val;\n }\n\n self.getCarry_ = function() {\n return regs[" + rF_ + "] & " + FLAG_C + ";\n };\n self.setCarry = function(val) {\n if (val) {\n regs[" + rF + "] |= " + FLAG_C + ";\n } else {\n regs[" + rF + "] &= " + (~FLAG_C) + ";\n }\n };\n self.getA_ = function() {\n return regs[" + rA_ + "];\n };\n\n return self;\n};"; + defineZ80JS = "window.Z80 = function(opts) {\n var self = {};\n\n " + setUpStateJS + "\n\n self.requestInterrupt = function(dataBus) {\n interruptPending = true;\n interruptDataBus = dataBus & 0xffff;\n /* TODO: use event scheduling to keep the interrupt line active for a fixed\n ~48T window, to support retriggered interrupts and interrupt blocking via\n chains of EI or DD/FD prefixes */\n }\n self.nonMaskableInterrupt = function() {\n iff1 = 1;\n self.requestInterrupt(0x66);\n }\n var z80Interrupt = function() {\n if (iff1) {\n if (halted) {\n /* move PC on from the HALT opcode */\n regPairs[" + rpPC + "]++;\n halted = false;\n }\n\n iff1 = iff2 = 0;\n\n /* push current PC in readiness for call to interrupt handler */\n regPairs[" + rpSP + "]--; WRITEMEM(regPairs[" + rpSP + "], regPairs[" + rpPC + "] >> 8);\n regPairs[" + rpSP + "]--; WRITEMEM(regPairs[" + rpSP + "], regPairs[" + rpPC + "] & 0xff);\n\n /* TODO: R register */\n\n switch (im) {\n case 0:\n regPairs[" + rpPC + "] = interruptDataBus; // assume always RST\n tstates += 6;\n break;\n case 1:\n regPairs[" + rpPC + "] = 0x0038;\n tstates += 7;\n break;\n case 2:\n inttemp = (regs[" + rI + "] << 8) | (interruptDataBus & 0xff);\n l = READMEM(inttemp);\n inttemp = (inttemp+1) & 0xffff;\n h = READMEM(inttemp);\n console.log(hex(interruptDataBus), hex(inttemp), hex(l), hex(h));\n regPairs[" + rpPC + "] = (h<<8) | l;\n tstates += 7;\n break;\n }\n }\n };\n\n self.runFrame = function(frameLength) {\n var lastOpcodePrefix, offset, opcode;\n\n while (tstates < frameLength || opcodePrefix) {\n if (interruptible && interruptPending) {\n z80Interrupt();\n interruptPending = false;\n }\n interruptible = true; /* unless overridden by opcode */\n lastOpcodePrefix = opcodePrefix;\n opcodePrefix = '';\n switch (lastOpcodePrefix) {\n case '':\n CONTEND_READ(regPairs[" + rpPC + "], 4);\n opcode = memory.read(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n regs[" + rR + "] = ((regs[" + rR + "] + 1) & 0x7f) | (regs[" + rR + "] & 0x80);\n " + (opcodeSwitch(OPCODE_RUN_STRINGS, null, opts.traps)) + "\n break;\n case 'CB':\n CONTEND_READ(regPairs[" + rpPC + "], 4);\n opcode = memory.read(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n regs[" + rR + "] = ((regs[" + rR + "] + 1) & 0x7f) | (regs[" + rR + "] & 0x80);\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_CB)) + "\n break;\n case 'DD':\n CONTEND_READ(regPairs[" + rpPC + "], 4);\n opcode = memory.read(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n regs[" + rR + "] = ((regs[" + rR + "] + 1) & 0x7f) | (regs[" + rR + "] & 0x80);\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_DD, OPCODE_RUN_STRINGS)) + "\n break;\n case 'DDCB':\n offset = READMEM(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n if (offset & 0x80) offset -= 0x100;\n CONTEND_READ(regPairs[" + rpPC + "], 3);\n opcode = memory.read(regPairs[" + rpPC + "]);\n CONTEND_READ_NO_MREQ(regPairs[" + rpPC + "], 1);\n CONTEND_READ_NO_MREQ(regPairs[" + rpPC + "], 1);\n regPairs[" + rpPC + "]++;\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_DDCB)) + "\n break;\n case 'ED':\n CONTEND_READ(regPairs[" + rpPC + "], 4);\n opcode = memory.read(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n regs[" + rR + "] = ((regs[" + rR + "] + 1) & 0x7f) | (regs[" + rR + "] & 0x80);\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_ED)) + "\n break;\n case 'FD':\n CONTEND_READ(regPairs[" + rpPC + "], 4);\n opcode = memory.read(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n regs[" + rR + "] = ((regs[" + rR + "] + 1) & 0x7f) | (regs[" + rR + "] & 0x80);\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_FD, OPCODE_RUN_STRINGS)) + "\n break;\n case 'FDCB':\n offset = READMEM(regPairs[" + rpPC + "]); regPairs[" + rpPC + "]++;\n if (offset & 0x80) offset -= 0x100;\n CONTEND_READ(regPairs[" + rpPC + "], 3);\n opcode = memory.read(regPairs[" + rpPC + "]);\n CONTEND_READ_NO_MREQ(regPairs[" + rpPC + "], 1);\n CONTEND_READ_NO_MREQ(regPairs[" + rpPC + "], 1);\n regPairs[" + rpPC + "]++;\n " + (opcodeSwitch(OPCODE_RUN_STRINGS_FDCB)) + "\n break;\n default:\n throw(\"Unknown opcode prefix: \" + lastOpcodePrefix);\n }\n }\n while (display.nextEventTime != null && display.nextEventTime <= tstates) display.doEvent();\n };\n\n self.reset = function() {\n regPairs[" + rpPC + "] = regPairs[" + rpIR + "] = 0;\n iff1 = 0; iff2 = 0; im = 0; halted = false;\n };\n\n self.loadState = function(snapRegs) {\n regPairs[" + rpAF + "] = snapRegs['AF'];\n regPairs[" + rpBC + "] = snapRegs['BC'];\n regPairs[" + rpDE + "] = snapRegs['DE'];\n regPairs[" + rpHL + "] = snapRegs['HL'];\n regPairs[" + rpAF_ + "] = snapRegs['AF_'];\n regPairs[" + rpBC_ + "] = snapRegs['BC_'];\n regPairs[" + rpDE_ + "] = snapRegs['DE_'];\n regPairs[" + rpHL_ + "] = snapRegs['HL_'];\n regPairs[" + rpIX + "] = snapRegs['IX'];\n regPairs[" + rpIY + "] = snapRegs['IY'];\n regPairs[" + rpSP + "] = snapRegs['SP'];\n regPairs[" + rpPC + "] = snapRegs['PC'];\n regPairs[" + rpIR + "] = snapRegs['IR'];\n iff1 = snapRegs['iff1'];\n iff2 = snapRegs['iff2'];\n im = snapRegs['im'];\n halted = snapRegs['halted'];\n tstates = snapRegs['tstates'];\n interruptPending = snapRegs['intp'];\n interruptDataBus = snapRegs['intd'];\n };\n\n self.saveState = function() {\n return {\n AF: regPairs[" + rpAF + "],\n BC: regPairs[" + rpBC + "],\n DE: regPairs[" + rpDE + "],\n HL: regPairs[" + rpHL + "],\n AF_: regPairs[" + rpAF_ + "],\n BC_: regPairs[" + rpBC_ + "],\n DE_: regPairs[" + rpDE_ + "],\n HL_: regPairs[" + rpHL_ + "],\n IX: regPairs[" + rpIX + "],\n IY: regPairs[" + rpIY + "],\n SP: regPairs[" + rpSP + "],\n PC: regPairs[" + rpPC + "],\n IR: regPairs[" + rpIR + "],\n iff1: iff1,\n iff2: iff2,\n im: im,\n halted: halted,\n tstates: tstates,\n intp: interruptPending,\n intd: interruptDataBus,\n };\n };\n\n /* Register / flag accessors (used for tape trapping and test harness) */\n self.getAF = function() {\n return regPairs[" + rpAF + "];\n }\n self.getBC = function() {\n return regPairs[" + rpBC + "];\n }\n self.getDE = function() {\n return regPairs[" + rpDE + "];\n }\n self.getHL = function() {\n return regPairs[" + rpHL + "];\n }\n self.getAF_ = function() {\n return regPairs[" + rpAF_ + "];\n }\n self.getBC_ = function() {\n return regPairs[" + rpBC_ + "];\n }\n self.getDE_ = function() {\n return regPairs[" + rpDE_ + "];\n }\n self.getHL_ = function() {\n return regPairs[" + rpHL_ + "];\n }\n self.getIX = function() {\n return regPairs[" + rpIX + "];\n }\n self.getIY = function() {\n return regPairs[" + rpIY + "];\n }\n self.getI = function() {\n return regs[" + rI + "];\n }\n self.getR = function() {\n return regs[" + rR + "];\n }\n self.getSP = function() {\n return regPairs[" + rpSP + "];\n }\n self.getPC = function() {\n return regPairs[" + rpPC + "];\n }\n self.getIFF1 = function() {\n return iff1;\n }\n self.getIFF2 = function() {\n return iff2;\n }\n self.getIM = function() {\n return im;\n }\n self.getHalted = function() {\n return halted;\n }\n\n self.setAF = function(val) {\n regPairs[" + rpAF + "] = val;\n }\n self.setBC = function(val) {\n regPairs[" + rpBC + "] = val;\n }\n self.setDE = function(val) {\n regPairs[" + rpDE + "] = val;\n }\n self.setHL = function(val) {\n regPairs[" + rpHL + "] = val;\n }\n self.setAF_ = function(val) {\n regPairs[" + rpAF_ + "] = val;\n }\n self.setBC_ = function(val) {\n regPairs[" + rpBC_ + "] = val;\n }\n self.setDE_ = function(val) {\n regPairs[" + rpDE_ + "] = val;\n }\n self.setHL_ = function(val) {\n regPairs[" + rpHL_ + "] = val;\n }\n self.setIX = function(val) {\n regPairs[" + rpIX + "] = val;\n }\n self.setIY = function(val) {\n regPairs[" + rpIY + "] = val;\n }\n self.setI = function(val) {\n regs[" + rI + "] = val;\n }\n self.setR = function(val) {\n regs[" + rR + "] = val;\n }\n self.setSP = function(val) {\n regPairs[" + rpSP + "] = val;\n }\n self.setPC = function(val) {\n regPairs[" + rpPC + "] = val;\n }\n self.setIFF1 = function(val) {\n iff1 = val;\n }\n self.setIFF2 = function(val) {\n iff2 = val;\n }\n self.setIM = function(val) {\n im = val;\n }\n self.setHalted = function(val) {\n halted = val;\n }\n\n self.getTstates = function() {\n return tstates;\n }\n self.setTstates = function(val) {\n tstates = val;\n }\n\n self.getCarry_ = function() {\n return regs[" + rF_ + "] & " + FLAG_C + ";\n };\n self.setCarry = function(val) {\n if (val) {\n regs[" + rF + "] |= " + FLAG_C + ";\n } else {\n regs[" + rF + "] &= " + (~FLAG_C) + ";\n }\n };\n self.getA_ = function() {\n return regs[" + rA_ + "];\n };\n\n return self;\n};"; defineZ80JS = defineZ80JS.replace(/READMEM\((.*?)\)/g, '(CONTEND_READ($1, 3), memory.read($1))'); defineZ80JS = defineZ80JS.replace(/WRITEMEM\((.*?),(.*?)\)/g, "CONTEND_WRITE($1, 3);\nwhile (display.nextEventTime != null && display.nextEventTime < tstates) display.doEvent();\nmemory.write($1,$2);"); if (opts.applyContention) { diff --git a/src/emu.js b/src/emu.js index 3a790b89..7e486441 100644 --- a/src/emu.js +++ b/src/emu.js @@ -16,9 +16,7 @@ function __createCanvas(mainElement, width, height) { // TODO var fsElement = document.createElement('div'); fsElement.style.position = "relative"; - fsElement.style.padding = "20px"; - if (height > width) - fsElement.style.margin = "20%"; // TODO + fsElement.style.padding = "5%"; fsElement.style.overflow = "hidden"; fsElement.style.background = "black"; @@ -43,7 +41,10 @@ var RasterVideo = function(mainElement, width, height, options) { this.create = function() { self.canvas = canvas = __createCanvas(mainElement, width, height); if (options && options.rotate) { + // TODO: aspect ratio? canvas.style.transform = "rotate("+options.rotate+"deg)"; + if (canvas.width > canvas.height) + canvas.style.paddingTop = canvas.style.paddingBottom = "10%"; } ctx = canvas.getContext('2d'); imageData = ctx.createImageData(width, height); @@ -68,9 +69,12 @@ var RasterVideo = function(mainElement, width, height, options) { return datau32; } - this.updateFrame = function() { + this.updateFrame = function(sx, sy, dx, dy, width, height) { imageData.data.set(buf8); - ctx.putImageData(imageData, 0, 0); + if (width && height) + ctx.putImageData(imageData, sx, sy, dx, dy, width, height); + else + ctx.putImageData(imageData, 0, 0); } /* diff --git a/src/platform/galaxian.js b/src/platform/galaxian.js new file mode 100644 index 00000000..fcf4c0bd --- /dev/null +++ b/src/platform/galaxian.js @@ -0,0 +1,381 @@ + +"use strict"; +var GALAXIAN_PRESETS = [ +]; + +// TODO: global??? +window.buildZ80({ + applyContention: false +}); + +var GalaxianPlatform = function(mainElement) { + var self = this; + this.__proto__ = new BaseZ80Platform(); + + var cpu, ram, vram, oram, membus, iobus, rom, palette, outlatches; + var video, audio, timer, pixels, displayPCs; + var inputs = [0xe,0x8,0x0]; + var interruptEnabled = 0; + var starsEnabled = 0; + var watchdog_counter; + var frameCounter = 0; + + var XTAL = 18432000.0; + var scanlinesPerFrame = 264; + var cpuFrequency = XTAL/6; // 3.072 MHz + var hsyncFrequency = XTAL/3/192/2; // 16 kHz + var vsyncFrequency = hsyncFrequency/132/2; // 60.606060 Hz + var vblankDuration = 1/vsyncFrequency * (20/132); // 2500 us + var cpuCyclesPerLine = cpuFrequency/hsyncFrequency; + var INITIAL_WATCHDOG = 256; + var showOffscreenObjects = false; + var stars = []; + for (var i=0; i<256; i++) + stars[i] = noise(); + + function drawScanline(pixels, sl) { + if (sl < 16 && !showOffscreenObjects) return; // offscreen + if (sl >= 240 && !showOffscreenObjects) return; // offscreen + // draw tiles + var pixofs = sl*264; + var outi = pixofs; // starting output pixel in frame buffer + for (var xx=0; xx<32; xx++) { + var xofs = xx; + var scroll = oram.mem[xofs*2]; // even entries control scroll position + var attrib = oram.mem[xofs*2+1]; // odd entries control the color base + var sl2 = (sl + scroll) & 0xff; + var vramofs = (sl2>>3)<<5; // offset in VRAM + var yy = sl2 & 7; // y offset within tile + var tile = vram.mem[vramofs+xofs]; + var color0 = (attrib & 7) << 2; + var addr = 0x2800+(tile<<3)+yy; + var data1 = rom[addr]; + var data2 = rom[addr+0x800]; + for (var i=0; i<8; i++) { + var bm = 128>>i; + var color = color0 + ((data1&bm)?1:0) + ((data2&bm)?2:0); + pixels[outi] = palette[color]; + outi++; + } + } + // draw sprites + for (var sprnum=7; sprnum>=0; sprnum--) { + var base = (sprnum<<2) + 0x40; + var base0 = oram.mem[base]; + var sy = 240 - (base0 - (sprnum<3)); // the first three sprites match against y-1 + var yy = (sl - sy); + if (yy >= 0 && yy < 16) { + var sx = oram.mem[base+3] + 1; // +1 pixel offset from tiles + if (sx == 0 && !showOffscreenObjects) + continue; // drawn off-buffer + var code = oram.mem[base+1]; + var flipx = code & 0x40; // TODO + var flipy = code & 0x80; // TODO + code &= 0x3f; + var color0 = oram.mem[base+2] << 2; + var addr = 0x2800+(code<<5)+(yy<8?yy:yy+8); + outi = pixofs + sx; //<< 1 + var data1 = rom[addr]; + var data2 = rom[addr+0x800]; + for (var i=0; i<8; i++) { + var bm = 128>>i; + var color = ((data1&bm)?1:0) + ((data2&bm)?2:0); + if (color) + pixels[outi+i] = palette[color0 + color]; + } + var data1 = rom[addr+8]; + var data2 = rom[addr+0x808]; + for (var i=0; i<8; i++) { + var bm = 128>>i; + var color = ((data1&bm)?1:0) + ((data2&bm)?2:0); + if (color) + pixels[outi+i+8] = palette[color0 + color]; + } + } + } + // draw bullets/shells + var shell = 0xff; + var missile = 0xff; + for (var which=0; which<8; which++) { + var sy = oram.mem[0x60 + (which<<2)+1]; + if (((sy + sl - (which<3))&0xff) == 0xff) { + if (which != 7) + shell = which; + else + missile = which; + } + } + for (var which of [shell,missile]) { + if (which != 0xff) { + var sx = 255 - oram.mem[0x60 + (which<<2)+3]; + var outi = pixofs+sx; + var col = which == 7 ? 0xffffff00 : 0xffffffff; + pixels[outi++] = col; + pixels[outi++] = col; + pixels[outi++] = col; + pixels[outi++] = col; + } + } + // draw stars + if (starsEnabled) { + var starx = ((frameCounter + stars[sl & 0xff]) & 0xff); + if ((starx + sl) & 0x10) { + var outi = pixofs + starx; + if ((pixels[outi] & 0xffffff) == 0) { + pixels[outi] = palette[sl & 0x1f]; + } + } + } + } + + var KEYCODE_MAP = { + 32:{i:0,b:4}, // space bar (P1) + 37:{i:0,b:2}, // left arrow (P1) + 39:{i:0,b:3}, // right arrow (P1) + 0x53:{i:1,b:4}, // S (P2) + 0x41:{i:1,b:2}, // A (P2) + 0x44:{i:1,b:3}, // D (P2) + 53:{i:0,b:0}, // 5 + 49:{i:1,b:0}, // 1 + 50:{i:1,b:1}, // 2 + } + + this.getPresets = function() { + return GALAXIAN_PRESETS; + } + + this.start = function() { + ram = new RAM(0x400); + vram = new RAM(0x400); + oram = new RAM(0x100); + outlatches = new RAM(0x8); + membus = { + read: function(address) { + if (address < 0x4000) { + return (rom ? rom[address] : 0) & 0xff; + } else if (address < 0x4800) { + address &= 0x3ff; + return ram.mem[address] & 0xff; + } else if (address >= 0x5000 && address < 0x5800) { + address &= 0x3ff; + return vram.mem[address] & 0xff; + } else if (address >= 0x5800 && address < 0x6000) { + address &= 0xff; + return oram.mem[address] & 0xff; + } else if (address >= 0x6000 && address < 0x6800) { + address &= 0x7; + switch (address) { + case 0: + return inputs[0]; + } + } else if (address >= 0x6800 && address < 0x7000) { + return inputs[1]; + } else if (address >= 0x7000 && address < 0x7800) { + address &= 0x7; + switch (address) { + case 0: + return inputs[2]; + } + } else if (address >= 0x7800 && address < 0x8000) { + watchdog_counter = INITIAL_WATCHDOG; + } else { + console.log("read", hex(address)); + return 0; + } + }, + write: function(address, value) { + //console.log("write", hex(address,4), hex(value,2)); + if (address >= 0x4000 && address < 0x4800) { + address &= 0x3ff; + ram.mem[address] = value; + } else if (address >= 0x5000 && address < 0x5800) { + address &= 0x3ff; + vram.mem[address] = value; + } else if (address >= 0x5800 && address < 0x6000) { + address &= 0xff; + oram.mem[address] = value; + } else if (address >= 0x6000 && address < 0x6800) { + address &= 0x7; + outlatches.mem[address] = value; + } else if (address >= 0x6800 && address < 0x7000) { + address &= 0x7; + // TODO: sound + } else if (address >= 0x7000 && address < 0x7800) { + address &= 0x7; + switch (address) { + case 1: + interruptEnabled = value; + break; + case 4: + starsEnabled = value; + break; + } + } else if (address >= 0x7800 && address < 0x8000) { + // TODO: sound + } else { + console.log("write", hex(address), hex(value)); + } + }, + isContended: function() { return false; }, + }; + iobus = { + read: function(addr) { + console.log('IO read', hex(addr,4)); + return 0; + }, + write: function(addr, val) { + console.log('IO write', hex(addr,4), hex(val,2)); + } + }; + cpu = window.Z80({ + display: {}, + memory: membus, + ioBus: iobus + }); + video = new RasterVideo(mainElement,264,264,{rotate:90}); + audio = new SampleAudio(cpuFrequency); + video.create(); + var idata = video.getFrameData(); + video.setKeyboardEvents(function(key,code,flags) { + var o = KEYCODE_MAP[key]; + if (o) { + if (flags & 1) { + inputs[o.i] |= (1<= 0) ; + var arr = new Uint8Array(new ArrayBuffer(l)); + for (var i=0; i 0) + return; worker.postMessage({code:text, platform:platform_id, tool:platform.getToolForFilename(current_preset_id)}); } @@ -328,6 +331,10 @@ function arrayCompare(a,b) { } worker.onmessage = function(e) { + if (pendingWorkerMessages > 1) { + setCode(editor.getValue()); + } + pendingWorkerMessages = 0; // errors? var toolbar = $("#controls_top"); function addErrorMarker(line, msg) {