PC8080 skeleton

This commit is contained in:
Jeff Parsons 2016-04-20 07:54:58 -07:00
commit 19b22d32d4
17 changed files with 12295 additions and 2 deletions

1043
modules/pc8080/lib/bus.js Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1147
modules/pc8080/lib/cpu.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,158 @@
/**
* @fileoverview Defines PC8080 constants.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2016-Apr-18
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <http://pcjs.org/>.
*
* PCjs 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 3
* of the License, or (at your option) any later version.
*
* PCjs 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 PCjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.COPYRIGHT).
*
* Some PCjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* PCjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
var CPUDef = {
/*
* CPU model numbers (supported)
*/
MODEL_8080: 8080,
/*
* This constant is used to mark points in the code where the physical address being returned
* is invalid and should not be used.
*
* In a 32-bit CPU, -1 (ie, 0xffffffff) could actually be a valid address, so consider changing
* ADDR_INVALID to NaN or null (which is also why all ADDR_INVALID tests should use strict equality
* operators).
*
* The main reason I'm NOT using NaN or null now is my concern that, by mixing non-numbers
* (specifically, values outside the range of signed 32-bit integers), performance may suffer.
*
* WARNING: Like many of the properties defined here, ADDR_INVALID is a common constant, which the
* Closure Compiler will happily inline (with or without @const annotations; in fact, I've yet to
* see a @const annotation EVER improve automatic inlining). However, if you don't make ABSOLUTELY
* certain that this file is included BEFORE the first reference to any of these properties, that
* automatic inlining will no longer occur.
*/
ADDR_INVALID: -1,
/*
* Processor Status flag definitions (stored in regPS)
*/
PS: {
CF: 0x0001, // bit 0: Carry flag
BIT1: 0x0002, // bit 1: reserved, always set
PF: 0x0004, // bit 2: Parity flag
BIT3: 0x0008, // bit 3: reserved, always clear
AF: 0x0010, // bit 4: Auxiliary Carry flag
BIT5: 0x0020, // bit 5: reserved, always clear
ZF: 0x0040, // bit 6: Zero flag
SF: 0x0080, // bit 7: Sign flag
ALL: 0x00D5, // CF, PF, AF, ZF, SF
MASK: 0x00FF, //
IF: 0x0200, // bit 9: Interrupt flag (for internal use only)
OF: 0x0800 // bit 11: Overflow flag (for internal use only)
},
RESULT: {
/*
* Flags are computed using the following internal registers:
*
* CF: resultZeroCarry & resultSize (ie, 0x100 or 0x10000)
* PF: resultParitySign & 0xff
* AF: (resultParitySign ^ resultAuxOverflow) & 0x0010
* ZF: resultZeroCarry & (resultSize - 1)
* SF: resultParitySign & (resultSize >> 1)
* OF: (resultParitySign ^ resultAuxOverflow ^ (resultParitySign >> 1)) & (resultSize >> 1)
*/
SIZE_BYTE: 0x00100, // mask for byte arithmetic instructions (after subtracting 1)
SIZE_WORD: 0x10000, // mask for word arithmetic instructions (after subtracting 1)
AUXOVF_AF: 0x00010,
AUXOVF_OF: 0x08080,
AUXOVF_CF: 0x10100
},
PARITY: [ // 256-byte array with a 1 wherever the number of set bits of the array index is EVEN
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1
],
/*
* Bit values for opFlags, which are all reset to zero prior to each instruction
*/
OPFLAG: {
NOREAD: 0x0001, // disable memory reads for the remainder of the current instruction
NOWRITE: 0x0002, // disable memory writes for the remainder of the current instruction
NOINTR: 0x0004 // a segreg has been set, or a prefix, or an STI (delay INTR acknowledgement)
},
/*
* Bit values for intFlags
*/
INTFLAG: {
NONE: 0x00,
INTR: 0x01, // h/w interrupt requested
HALT: 0x04 // halt (HLT) requested
},
/*
* Opcode definitions
*/
OPCODE: {
ACI: 0xCE, // PS.ALL
CALL: 0xCD
// to be continued....
},
CYCLES: {
ACI: 7 // 2 cycles, 7 states
// to be continued....
}
};
/*
* Some PS flags are stored directly in regPS, hence the "direct" designation.
*/
CPUDef.PS.DIRECT = (CPUDef.PS.IF);
/*
* However, PS "arithmetic" flags are NOT stored in regPS; they are maintained across
* separate result registers, hence the "indirect" designation.
*/
CPUDef.PS.INDIRECT = (CPUDef.PS.CF | CPUDef.PS.PF | CPUDef.PS.AF | CPUDef.PS.ZF | CPUDef.PS.SF | CPUDef.PS.OF);
/*
* These are the default "always set" PS bits for the 8080.
*/
CPUDef.PS.SET = (CPUDef.PS.BIT1);
if (NODE) module.exports = CPUDef;

1104
modules/pc8080/lib/cpusim.js Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,81 @@
/**
* @fileoverview PC8080-specific compile-time definitions.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2016-Apr-18
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <http://pcjs.org/>.
*
* PCjs 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 3
* of the License, or (at your option) any later version.
*
* PCjs 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 PCjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.COPYRIGHT).
*
* Some PCjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* PCjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
/**
* @define {string}
*/
var PCJSCLASS = "pc8080"; // this @define is the default application class (formerly APPCLASS) to use
/**
* @define {boolean}
*
* WARNING: DEBUGGER needs to accurately reflect whether or not the Debugger component is (or will be) loaded.
* In the compiled case, we rely on the Closure Compiler to override DEBUGGER as appropriate. When it's *false*,
* nearly all of debugger.js will be conditionally removed by the compiler, reducing it to little more than a
* "type skeleton", which also solves some type-related warnings we would otherwise have if we tried to remove
* debugger.js from the compilation process altogether.
*
* However, when we're in "development mode" and running uncompiled code in debugger-less configurations,
* I would like to skip loading debugger.js altogether. When doing that, we must ALSO arrange for an additional file
* (nodebugger.js) to be loaded immediately after this file, which *explicitly* overrides DEBUGGER with *false*.
*/
var DEBUGGER = true; // this @define is overridden by the Closure Compiler to remove Debugger-related support
/**
* @define {boolean}
*
* BYTEARRAYS is a Closure Compiler compile-time option that allocates an Array of numbers for every Memory block,
* where each a number represents ONE byte; very wasteful, but potentially slightly faster.
*
* See the Memory component for details.
*/
var BYTEARRAYS = false;
/**
* TYPEDARRAYS enables use of typed arrays for Memory blocks. This used to be a compile-time-only option, but I've
* added Memory access functions for typed arrays (see Memory.afnTypedArray), so support can be enabled dynamically now.
*
* See the Memory component for details.
*/
var TYPEDARRAYS = (typeof ArrayBuffer !== 'undefined');
if (NODE) {
global.PCJSCLASS = PCJSCLASS;
global.DEBUGGER = DEBUGGER;
global.BYTEARRAYS = BYTEARRAYS;
global.TYPEDARRAYS = TYPEDARRAYS;
/*
* TODO: When we're "required" by Node, should we return anything via module.exports?
*/
}

View file

@ -0,0 +1,237 @@
/**
* @fileoverview Implements the PC8080 Keyboard component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2016-Apr-19
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <http://pcjs.org/>.
*
* PCjs 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 3
* of the License, or (at your option) any later version.
*
* PCjs 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 PCjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.COPYRIGHT).
*
* Some PCjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* PCjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
if (NODE) {
var str = require("../../shared/lib/strlib");
var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var Messages = require("./messages");
var State = require("./state");
var CPU = require("./cpu");
}
/**
* Keyboard(parmsKbd)
*
* @constructor
* @extends Component
* @param {Object} parmsKbd
*/
function Keyboard(parmsKbd)
{
Component.call(this, "Keyboard", parmsKbd, Keyboard, Messages.KEYBOARD);
this.setReady();
}
Component.subclass(Keyboard);
/**
* Alphanumeric and other common (printable) ASCII codes.
*
* TODO: Determine what we can do to get ALL constants like these inlined (enum doesn't seem to
* get the job done); the problem seems to be limited to property references that use quotes, which
* is why I've 'unquoted' as many of them as possible.
*
* @enum {number}
*/
Keyboard.ASCII = {
CTRL_A: 1, CTRL_C: 3, CTRL_Z: 26,
' ': 32, '!': 33, '"': 34, '#': 35, '$': 36, '%': 37, '&': 38, "'": 39,
'(': 40, ')': 41, '*': 42, '+': 43, ',': 44, '-': 45, '.': 46, '/': 47,
'0': 48, '1': 49, '2': 50, '3': 51, '4': 52, '5': 53, '6': 54, '7': 55,
'8': 56, '9': 57, ':': 58, ';': 59, '<': 60, '=': 61, '>': 62, '?': 63,
'@': 64, A: 65, B: 66, C: 67, D: 68, E: 69, F: 70, G: 71,
H: 72, I: 73, J: 74, K: 75, L: 76, M: 77, N: 78, O: 79,
P: 80, Q: 81, R: 82, S: 83, T: 84, U: 85, V: 86, W: 87,
X: 88, Y: 89, Z: 90, '[': 91, '\\':92, ']': 93, '^': 94, '_': 95,
'`': 96, a: 97, b: 98, c: 99, d: 100, e: 101, f: 102, g: 103,
h: 104, i: 105, j: 106, k: 107, l: 108, m: 109, n: 110, o: 111,
p: 112, q: 113, r: 114, s: 115, t: 116, u: 117, v: 118, w: 119,
x: 120, y: 121, z: 122, '{':123, '|':124, '}':125, '~':126
};
/**
* Browser keyCodes we must pay particular attention to. For the most part, these are non-alphanumeric
* or function keys, some which may require special treatment (eg, preventDefault() if returning false on
* the initial keyDown event is insufficient).
*
* keyCodes for most common ASCII keys can simply use the appropriate ASCII code above.
*
* Most of these represent non-ASCII keys (eg, the LEFT arrow key), yet for some reason, browsers defined
* them using ASCII codes (eg, the LEFT arrow key uses the ASCII code for '%' or 37). This conflict is
* discussed further in the definition of CLICKCODE below.
*
* @enum {number}
*/
Keyboard.KEYCODE = {
/* 0x08 */ BS: 8,
/* 0x09 */ TAB: 9,
/* 0x0A */ LF: 10,
/* 0x0D */ CR: 13,
/* 0x10 */ SHIFT: 16,
/* 0x11 */ CTRL: 17,
/* 0x12 */ ALT: 18,
/* 0x13 */ PAUSE: 19, // PAUSE/BREAK
/* 0x14 */ CAPS_LOCK: 20,
/* 0x1B */ ESC: 27,
/* 0x20 */ SPACE: 32,
/* 0x21 */ PGUP: 33,
/* 0x22 */ PGDN: 34,
/* 0x23 */ END: 35,
/* 0x24 */ HOME: 36,
/* 0x25 */ LEFT: 37,
/* 0x26 */ UP: 38,
/* 0x27 */ RIGHT: 39,
/* 0x27 */ FF_QUOTE: 39,
/* 0x28 */ DOWN: 40,
/* 0x2C */ FF_COMMA: 44,
/* 0x2C */ PRTSC: 44,
/* 0x2D */ INS: 45,
/* 0x2E */ DEL: 46,
/* 0x2E */ FF_PERIOD: 46,
/* 0x2F */ FF_SLASH: 47,
/* 0x3B */ FF_SEMI: 59,
/* 0x3D */ FF_EQUALS: 61,
/* 0x5B */ CMD: 91, // aka WIN
/* 0x5B */ FF_LBRACK: 91,
/* 0x5C */ FF_BSLASH: 92,
/* 0x5D */ RCMD: 93, // aka MENU
/* 0x5D */ FF_RBRACK: 93,
/* 0x60 */ NUM_INS: 96, // 0
/* 0x60 */ FF_BQUOTE: 96,
/* 0x61 */ NUM_END: 97, // 1
/* 0x62 */ NUM_DOWN: 98, // 2
/* 0x63 */ NUM_PGDN: 99, // 3
/* 0x64 */ NUM_LEFT: 100, // 4
/* 0x65 */ NUM_CENTER: 101, // 5
/* 0x66 */ NUM_RIGHT: 102, // 6
/* 0x67 */ NUM_HOME: 103, // 7
/* 0x68 */ NUM_UP: 104, // 8
/* 0x69 */ NUM_PGUP: 105, // 9
/* 0x6A */ NUM_MUL: 106,
/* 0x6B */ NUM_ADD: 107,
/* 0x6D */ NUM_SUB: 109,
/* 0x6E */ NUM_DEL: 110, // .
/* 0x6F */ NUM_DIV: 111,
/* 0x70 */ F1: 112,
/* 0x71 */ F2: 113,
/* 0x72 */ F3: 114,
/* 0x73 */ F4: 115,
/* 0x74 */ F5: 116,
/* 0x75 */ F6: 117,
/* 0x76 */ F7: 118,
/* 0x77 */ F8: 119,
/* 0x78 */ F9: 120,
/* 0x79 */ F10: 121,
/* 0x7A */ F11: 122,
/* 0x7B */ F12: 123,
/* 0x90 */ NUM_LOCK: 144,
/* 0x91 */ SCROLL_LOCK: 145,
/* 0xAD */ FF_DASH: 173,
/* 0xBA */ SEMI: 186, // Firefox: 59
/* 0xBB */ EQUALS: 187, // Firefox: 61
/* 0xBC */ COMMA: 188, // Firefox: 44
/* 0xBD */ DASH: 189, // Firefox: 173
/* 0xBE */ PERIOD: 190, // Firefox: 46
/* 0xBF */ SLASH: 191, // Firefox: 47
/* 0xC0 */ BQUOTE: 192, // Firefox: 96
/* 0xDB */ LBRACK: 219, // Firefox: 91
/* 0xDC */ BSLASH: 220, // Firefox: 92
/* 0xDD */ RBRACK: 221, // Firefox: 93
/* 0xDE */ QUOTE: 222, // Firefox: 39
/* 0xE0 */ FF_CMD: 224, // Firefox only (used for both CMD and RCMD)
//
// The following biases use what I'll call Decimal Coded Binary or DCB (the opposite of BCD),
// where the thousands digit is used to store the sum of "binary" digits 1 and/or 2 and/or 4.
//
// Technically, that makes it DCO (Decimal Coded Octal), but then again, BCD should have really
// been called HCD (Hexadecimal Coded Decimal), so if "they" can take liberties, so can I.
//
// ONDOWN is a bias we add to browser keyCodes that we want to handle on "down" rather than on "press".
//
ONDOWN: 1000,
//
// ONRIGHT is a bias we add to browser keyCodes that need to check for a "right" location (default is "left")
//
ONRIGHT: 2000,
//
// FAKE is a bias we add to signal these are fake keyCodes corresponding to internal keystroke combinations.
// The actual values are for internal use only and merely need to be unique and used consistently.
//
FAKE: 4000
};
/*
* Maps "stupid" keyCodes to their "non-stupid" counterparts
*/
Keyboard.STUPID_KEYCODES = {};
Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.SEMI] = Keyboard.ASCII[';']; // 186 -> 59
Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.EQUALS] = Keyboard.ASCII['=']; // 187 -> 61
Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.COMMA] = Keyboard.ASCII[',']; // 188 -> 44
Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.DASH] = Keyboard.ASCII['-']; // 189 -> 45
Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.PERIOD] = Keyboard.ASCII['.']; // 190 -> 46
Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.SLASH] = Keyboard.ASCII['/']; // 191 -> 47
Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.BQUOTE] = Keyboard.ASCII['`']; // 192 -> 96
Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.LBRACK] = Keyboard.ASCII['[']; // 219 -> 91
Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.BSLASH] = Keyboard.ASCII['\\']; // 220 -> 92
Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.RBRACK] = Keyboard.ASCII[']']; // 221 -> 93
Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.QUOTE] = Keyboard.ASCII["'"]; // 222 -> 39
Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.FF_DASH] = Keyboard.ASCII['-'];
/**
* Keyboard.init()
*
* This function operates on every HTML element of class "keyboard", extracting the
* JSON-encoded parameters for the Keyboard constructor from the element's "data-value"
* attribute, invoking the constructor to create a Keyboard component, and then binding
* any associated HTML controls to the new component.
*/
Keyboard.init = function()
{
var aeKbd = Component.getElementsByClass(document, PCJSCLASS, "keyboard");
for (var iKbd = 0; iKbd < aeKbd.length; iKbd++) {
var eKbd = aeKbd[iKbd];
var parmsKbd = Component.getComponentParms(eKbd);
var kbd = new Keyboard(parmsKbd);
Component.bindComponentControls(kbd, eKbd, PCJSCLASS);
}
};
/*
* Initialize every Keyboard module on the page.
*/
web.onInit(Keyboard.init);
if (NODE) module.exports = Keyboard;

View file

@ -0,0 +1,825 @@
/**
* @fileoverview Implements the PC8080 Memory component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2016-Apr-18
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <http://pcjs.org/>.
*
* PCjs 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 3
* of the License, or (at your option) any later version.
*
* PCjs 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 PCjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.COPYRIGHT).
*
* Some PCjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* PCjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
if (NODE) {
var str = require("../../shared/lib/strlib");
var Component = require("../../shared/lib/component");
var Messages = require("./messages");
var CPUDef = require("./cpudef");
}
/**
* @class DataView
* @property {function(number,boolean):number} getUint8
* @property {function(number,number,boolean)} setUint8
* @property {function(number,boolean):number} getUint16
* @property {function(number,number,boolean)} setUint16
* @property {function(number,boolean):number} getInt32
* @property {function(number,number,boolean)} setInt32
*/
var littleEndian = (TYPEDARRAYS? (function() {
var buffer = new ArrayBuffer(2);
new DataView(buffer).setUint16(0, 256, true);
return new Uint16Array(buffer)[0] === 256;
})() : false);
/**
* Memory(addr, used, size, type)
*
* The Bus component allocates Memory objects so that each has a memory buffer with a
* block-granular starting address and an address range equal to bus.nBlockSize; however,
* the size of any given Memory object's underlying buffer can be either zero or bus.nBlockSize;
* memory read/write functions for empty (buffer-less) blocks are mapped to readNone/writeNone.
*
* The Bus allocates empty blocks for the entire address space during initialization, so that
* any reads/writes to undefined addresses will have no effect. Later, the ROM and RAM
* components will ask the Bus to allocate memory for specific ranges, and the Bus will allocate
* as many new blockSize Memory objects as the ranges require. Partial Memory blocks could
* also be supported in theory, but in practice, they're not.
*
* Because Memory blocks now allow us to have a "sparse" address space, we could choose to
* take the memory hit of allocating 4K arrays per block, where each element stores only one byte,
* instead of the more frugal but slightly slower approach of allocating arrays of 32-bit dwords
* (LONGARRAYS) and shifting/masking bytes/words to/from dwords; in theory, byte accesses would
* be faster and word accesses somewhat less faster.
*
* However, preliminary testing of that feature (BYTEARRAYS) did not yield significantly faster
* performance, so it is OFF by default to minimize our memory consumption. Using TYPEDARRAYS
* would seem best, but as discussed in defines.js, it's off by default, because it doesn't perform
* as well as LONGARRAYS; the other advantage of TYPEDARRAYS is that it should theoretically use
* about 1/2 the memory of LONGARRAYS (32-bit elements vs 64-bit numbers), but I value speed over
* size at this point. Also, not all JavaScript implementations support TYPEDARRAYS (IE9 is probably
* the only real outlier: it lacks typed arrays but otherwise has all the necessary HTML5 support).
*
* WARNING: Since Memory blocks are low-level objects that have no UI requirements, they
* do not inherit from the Component class, so if you want to use any Component class methods,
* such as Component.assert(), use the corresponding Debugger methods instead (assuming a debugger
* is available).
*
* @constructor
* @param {number|null} [addr] of lowest used address in block
* @param {number} [used] portion of block in bytes (0 for none); must be a multiple of 4
* @param {number} [size] of block's buffer in bytes (0 for none); must be a multiple of 4
* @param {number} [type] is one of the Memory.TYPE constants (default is Memory.TYPE.NONE)
*/
function Memory(addr, used, size, type)
{
var i;
this.id = (Memory.idBlock += 2);
this.adw = null;
this.offset = 0;
this.addr = addr;
this.used = used;
this.size = size || 0;
this.type = type || Memory.TYPE.NONE;
this.fReadOnly = (type == Memory.TYPE.ROM);
this.copyBreakpoints(); // initialize the block's Debugger info; the caller will reinitialize
/*
* TODO: Study the impact of dirty block tracking. The original purposes were to allow saveMemory()
* to save only dirty blocks, and to enable the Video component to quickly detect changes to the video buffer.
* But the benefit to saveMemory() is minimal, and the Video component has other options; for example, it now
* uses a custom memory controller for all EGA/VGA video modes, which performs its own dirty block tracking,
* and that could easily be extended to the older MDA/CGA video modes, which still use conventional memory blocks.
* Alternatively, we could restrict the use of dirty block tracking to certain memory types (eg, VIDEO memory).
*
* However, a quick test with dirty block tracking disabled didn't yield a noticeable improvement in performance,
* so I think the overhead of our block-based architecture is swamping the impact of these micro-updates.
*/
this.fDirty = this.fDirtyEver = false;
/*
* For empty memory blocks, all we need to do is ensure all access functions are mapped to "none" handlers.
*/
if (!size) {
this.setAccess();
return;
}
/*
* This is the normal case: allocate a buffer that provides 8 bits of data per address;
* no controller is required because our default memory access functions (see afnMemory)
* know how to deal with this simple 1-1 mapping of addresses to bytes and words.
*
* TODO: Consider initializing the memory array to random (or pseudo-random) values in DEBUG
* mode; pseudo-random might be best, to help make any bugs reproducible.
*/
if (TYPEDARRAYS) {
this.buffer = new ArrayBuffer(size);
this.dv = new DataView(this.buffer, 0, size);
/*
* If littleEndian is true, we can use ab[], aw[] and adw[] directly; well, we can use them
* whenever the offset is a multiple of 1, 2 or 4, respectively. Otherwise, we must fallback to
* dv.getUint8()/dv.setUint8(), dv.getUint16()/dv.setUint16() and dv.getInt32()/dv.setInt32().
*/
this.ab = new Uint8Array(this.buffer, 0, size);
this.aw = new Uint16Array(this.buffer, 0, size >> 1);
this.adw = new Int32Array(this.buffer, 0, size >> 2);
this.setAccess(littleEndian? Memory.afnArrayLE : Memory.afnArrayBE);
} else {
if (BYTEARRAYS) {
this.ab = new Array(size);
} else {
/*
* NOTE: This is the default mode of operation (!TYPEDARRAYS && !BYTEARRAYS), because it
* seems to provide the best performance; and although in theory, that performance might
* come at twice the overhead of TYPEDARRAYS, it's increasingly likely that the JavaScript
* runtime will notice that all we ever store are 32-bit values, and optimize accordingly.
*/
this.adw = new Array(size >> 2);
for (i = 0; i < this.adw.length; i++) this.adw[i] = 0;
}
this.setAccess(Memory.afnMemory);
}
}
/*
* Basic memory types
*
* RAM is the most conventional memory type, providing full read/write capability to x86-compatible (ie,
* 'little endian") storage. ROM is equally conventional, except that the fReadOnly property is set,
* disabling writes. VIDEO is treated exactly like RAM, unless a controller is provided. Both RAM and
* VIDEO memory are always considered writable, and even ROM can be written using the Bus setByteDirect()
* interface (which in turn uses the Memory writeByteDirect() interface), allowing the ROM component to
* initialize its own memory. The CTRL type is used to identify memory-mapped devices that do not need
* any default storage and always provide their own controller.
*
* Unallocated regions of the address space contain a special memory block of type NONE that contains
* no storage. Mapping every addressible location to a memory block allows all accesses to be routed in
* exactly the same manner, without resorting to any range or processor checks.
*
* These types are not mutually exclusive. For example, VIDEO memory could be allocated as RAM, with or
* without a custom controller (the original Monochrome and CGA video cards used read/write storage that
* was indistinguishable from RAM), and CTRL memory could be allocated as an empty block of any type, with
* a custom controller. A few types are required for certain features (eg, ROM is required if you want
* read-only memory), but the larger purpose of these types is to help document the caller's intent and to
* provide the Control Panel with the ability to highlight memory regions accordingly.
*/
Memory.TYPE = {
NONE: 0,
RAM: 1,
ROM: 2,
VIDEO: 3,
CTRL: 4,
NAMES: ["NONE", "RAM", "ROM", "VIDEO", "H/W"],
COLORS: ["black", "blue", "green", "cyan"]
};
/*
* Last used block ID (used for debugging only)
*/
Memory.idBlock = 0;
/**
* adjustEndian(dw)
*
* @param {number} dw
* @return {number}
*/
Memory.adjustEndian = function(dw) {
if (TYPEDARRAYS && !littleEndian) {
dw = (dw << 24) | ((dw << 8) & 0x00ff0000) | ((dw >> 8) & 0x0000ff00) | (dw >>> 24);
}
return dw;
};
Memory.prototype = {
constructor: Memory,
parent: null,
/**
* init(addr)
*
* Quick reinitializer when reusing a Memory block.
*
* @this {Memory}
* @param {number} addr
*/
init: function(addr) {
this.addr = addr;
},
/**
* clone(mem, type)
*
* Converts the current Memory block (this) into a clone of the given Memory block (mem),
* and optionally overrides the current block's type with the specified type.
*
* @this {Memory}
* @param {Memory} mem
* @param {number} [type]
* @param {Debugger} [dbg]
*/
clone: function(mem, type, dbg) {
/*
* Original memory block IDs are even; cloned memory block IDs are odd;
* the original ID of the current block is lost, but that's OK, since it was presumably
* produced merely to become a clone.
*/
this.id = mem.id | 0x1;
this.used = mem.used;
this.size = mem.size;
if (type) {
this.type = type;
this.fReadOnly = (type == Memory.TYPE.ROM);
}
if (TYPEDARRAYS) {
this.buffer = mem.buffer;
this.dv = mem.dv;
this.ab = mem.ab;
this.aw = mem.aw;
this.adw = mem.adw;
this.setAccess(littleEndian? Memory.afnArrayLE : Memory.afnArrayBE);
} else {
if (BYTEARRAYS) {
this.ab = mem.ab;
} else {
this.adw = mem.adw;
}
this.setAccess(Memory.afnMemory);
}
this.copyBreakpoints(dbg, mem);
},
/**
* save()
*
* This gets the contents of a Memory block as an array of 32-bit values; used by Bus.saveMemory(),
* which in turn is called by CPUSim.save().
*
* Memory blocks with custom memory controllers do NOT save their contents; that's the responsibility
* of the controller component.
*
* @this {Memory}
* @return {Array|Int32Array|null}
*/
save: function() {
var adw, i;
if (BYTEARRAYS) {
adw = new Array(this.size >> 2);
var off = 0;
for (i = 0; i < adw.length; i++) {
adw[i] = this.ab[off] | (this.ab[off + 1] << 8) | (this.ab[off + 2] << 16) | (this.ab[off + 3] << 24);
off += 4;
}
}
else if (TYPEDARRAYS) {
/*
* It might be tempting to just return a copy of Int32Array(this.buffer, 0, this.size >> 2),
* but we can't be sure of the "endianness" of an Int32Array -- which would be OK if the array
* was always saved/restored on the same machine, but there's no guarantee of that, either.
* So we use getInt32() and require little-endian values.
*
* Moreover, an Int32Array isn't treated by JSON.stringify() and JSON.parse() exactly like
* a normal array; it's serialized as an Object rather than an Array, so it lacks a "length"
* property and causes problems for State.store() and State.parse().
*/
adw = new Array(this.size >> 2);
for (i = 0; i < adw.length; i++) {
adw[i] = this.dv.getInt32(i << 2, true);
}
}
else {
adw = this.adw;
}
return adw;
},
/**
* restore(adw)
*
* This restores the contents of a Memory block from an array of 32-bit values;
* used by Bus.restoreMemory(), which is called by CPUSim.restore(), after all other
* components have been restored and thus all Memory blocks have been allocated
* by their respective components.
*
* @this {Memory}
* @param {Array|null} adw
* @return {boolean} true if successful, false if block size mismatch
*/
restore: function(adw) {
/*
* At this point, it's a consistency error for adw to be null; it's happened once already,
* when there was a restore bug in the Video component that added the frame buffer at the video
* card's "spec'ed" address instead of the programmed address, so there were no controller-owned
* memory blocks installed at the programmed address, and so we arrived here at a block with
* no controller AND no data.
*/
Component.assert(adw != null);
if (adw && this.size == adw.length << 2) {
var i;
if (BYTEARRAYS) {
var off = 0;
for (i = 0; i < adw.length; i++) {
this.ab[off] = adw[i] & 0xff;
this.ab[off + 1] = (adw[i] >> 8) & 0xff;
this.ab[off + 2] = (adw[i] >> 16) & 0xff;
this.ab[off + 3] = (adw[i] >> 24) & 0xff;
off += 4;
}
} else if (TYPEDARRAYS) {
for (i = 0; i < adw.length; i++) {
this.dv.setInt32(i << 2, adw[i], true);
}
} else {
this.adw = adw;
}
this.fDirty = true;
return true;
}
return false;
},
/**
* setAccess(afn, fDirect)
*
* If no function table is specified, a default is selected based on the Memory type.
*
* @this {Memory}
* @param {Array.<function()>} [afn] function table
* @param {boolean} [fDirect] (true to update direct access functions as well; default is true)
*/
setAccess: function(afn, fDirect) {
if (!afn) {
Component.assert(this.type == Memory.TYPE.NONE);
afn = Memory.afnNone;
}
this.setReadAccess(afn, fDirect);
this.setWriteAccess(afn, fDirect);
},
/**
* setReadAccess(afn, fDirect)
*
* @this {Memory}
* @param {Array.<function()>} afn
* @param {boolean} [fDirect]
*/
setReadAccess: function(afn, fDirect) {
if (!fDirect || !this.cReadBreakpoints) {
this.readByte = afn[0] || this.readNone;
this.readShort = afn[1] || this.readShortDefault;
}
if (fDirect || fDirect === undefined) {
this.readByteDirect = afn[0] || this.readNone;
this.readShortDirect = afn[1] || this.readShortDefault;
}
},
/**
* setWriteAccess(afn, fDirect)
*
* @this {Memory}
* @param {Array.<function()>} afn
* @param {boolean} [fDirect]
*/
setWriteAccess: function(afn, fDirect) {
if (!fDirect || !this.cWriteBreakpoints) {
this.writeByte = !this.fReadOnly && afn[2] || this.writeNone;
this.writeShort = !this.fReadOnly && afn[3] || this.writeShortDefault;
}
if (fDirect || fDirect === undefined) {
this.writeByteDirect = afn[2] || this.writeNone;
this.writeShortDirect = afn[3] || this.writeShortDefault;
}
},
/**
* resetReadAccess()
*
* @this {Memory}
*/
resetReadAccess: function() {
this.readByte = this.readByteDirect;
this.readShort = this.readShortDirect;
},
/**
* resetWriteAccess()
*
* @this {Memory}
*/
resetWriteAccess: function() {
this.writeByte = this.fReadOnly? this.writeNone : this.writeByteDirect;
this.writeShort = this.fReadOnly? this.writeShortDefault : this.writeShortDirect;
},
/**
* printAddr(sMessage)
*
* @this {Memory}
* @param {string} sMessage
*/
printAddr: function(sMessage) {
if (DEBUG && this.dbg && this.dbg.messageEnabled(Messages.MEM)) {
this.dbg.printMessage(sMessage + ' ' + (this.addr != null? ('%' + str.toHex(this.addr)) : '#' + this.id), true);
}
},
/**
* addBreakpoint(off, fWrite)
*
* @this {Memory}
* @param {number} off
* @param {boolean} fWrite
*/
addBreakpoint: function(off, fWrite) {
if (!fWrite) {
if (this.cReadBreakpoints++ === 0) {
this.setReadAccess(Memory.afnChecked, false);
}
if (DEBUG) this.printAddr("read breakpoint added to memory block");
}
else {
if (this.cWriteBreakpoints++ === 0) {
this.setWriteAccess(Memory.afnChecked, false);
}
if (DEBUG) this.printAddr("write breakpoint added to memory block");
}
},
/**
* removeBreakpoint(off, fWrite)
*
* @this {Memory}
* @param {number} off
* @param {boolean} fWrite
*/
removeBreakpoint: function(off, fWrite) {
if (!fWrite) {
if (--this.cReadBreakpoints === 0) {
this.resetReadAccess();
if (DEBUG) this.printAddr("all read breakpoints removed from memory block");
}
Component.assert(this.cReadBreakpoints >= 0);
}
else {
if (--this.cWriteBreakpoints === 0) {
this.resetWriteAccess();
if (DEBUG) this.printAddr("all write breakpoints removed from memory block");
}
Component.assert(this.cWriteBreakpoints >= 0);
}
},
/**
* copyBreakpoints(dbg, mem)
*
* @this {Memory}
* @param {Debugger} [dbg]
* @param {Memory} [mem] (outgoing Memory block to copy breakpoints from, if any)
*/
copyBreakpoints: function(dbg, mem) {
this.dbg = dbg;
this.cReadBreakpoints = this.cWriteBreakpoints = 0;
if (mem) {
if ((this.cReadBreakpoints = mem.cReadBreakpoints)) {
this.setReadAccess(Memory.afnChecked, false);
}
if ((this.cWriteBreakpoints = mem.cWriteBreakpoints)) {
this.setWriteAccess(Memory.afnChecked, false);
}
}
},
/**
* readNone(off)
*
* Previously, this always returned 0x00, but the initial memory probe by the COMPAQ DeskPro 386 ROM BIOS
* writes 0x0000 to the first word of every 64Kb block in the nearly 16Mb address space it supports, and
* if it reads back 0x0000, it will initially think that LOTS of RAM exists, only to be disappointed later
* when it performs a more exhaustive memory test, generating unwanted error messages in the process.
*
* TODO: Determine if we should have separate readByteNone(), readShortNone() and readLongNone() functions
* to return 0xff, 0xffff and 0xffffffff|0, respectively. This seems sufficient for now, as it seems unlikely
* that a system would require nonexistent memory locations to return ALL bits set.
*
* Also, I'm reluctant to address that potential issue by simply returning -1, because to date, the above
* Memory interfaces have always returned values that are properly masked to 8, 16 or 32 bits, respectively.
*
* @this {Memory}
* @param {number} off
* @param {number} addr
* @return {number}
*/
readNone: function readNone(off, addr) {
if (DEBUGGER && this.dbg && this.dbg.messageEnabled(Messages.CPU | Messages.MEM) /* && !off */) {
this.dbg.message("attempt to read invalid block %" + str.toHex(this.addr), true);
}
return 0xff;
},
/**
* writeNone(off, v, addr)
*
* @this {Memory}
* @param {number} off
* @param {number} v (could be either a byte or word value, since we use the same handler for both kinds of accesses)
* @param {number} addr
*/
writeNone: function writeNone(off, v, addr) {
if (DEBUGGER && this.dbg && this.dbg.messageEnabled(Messages.CPU | Messages.MEM) /* && !off */) {
this.dbg.message("attempt to write " + str.toHexWord(v) + " to invalid block %" + str.toHex(this.addr), true);
}
},
/**
* readShortDefault(off, addr)
*
* @this {Memory}
* @param {number} off
* @param {number} addr
* @return {number}
*/
readShortDefault: function readShortDefault(off, addr) {
return this.readByte(off++, addr++) | (this.readByte(off, addr) << 8);
},
/**
* writeShortDefault(off, w, addr)
*
* @this {Memory}
* @param {number} off
* @param {number} w
* @param {number} addr
*/
writeShortDefault: function writeShortDefault(off, w, addr) {
this.writeByte(off++, w & 0xff, addr++);
this.writeByte(off, w >> 8, addr);
},
/**
* readByteMemory(off, addr)
*
* @this {Memory}
* @param {number} off
* @param {number} addr
* @return {number}
*/
readByteMemory: function readByteMemory(off, addr) {
if (BYTEARRAYS) {
return this.ab[off];
}
return ((this.adw[off >> 2] >>> ((off & 0x3) << 3)) & 0xff);
},
/**
* readShortMemory(off, addr)
*
* @this {Memory}
* @param {number} off
* @param {number} addr
* @return {number}
*/
readShortMemory: function readShortMemory(off, addr) {
if (BYTEARRAYS) {
return this.ab[off] | (this.ab[off + 1] << 8);
}
var w;
var idw = off >> 2;
var nShift = (off & 0x3) << 3;
var dw = (this.adw[idw] >> nShift);
if (nShift < 24) {
w = dw & 0xffff;
} else {
w = (dw & 0xff) | ((this.adw[idw + 1] & 0xff) << 8);
}
return w;
},
/**
* writeByteMemory(off, b, addr)
*
* @this {Memory}
* @param {number} off
* @param {number} b
* @param {number} addr
*/
writeByteMemory: function writeByteMemory(off, b, addr) {
if (BYTEARRAYS) {
this.ab[off] = b;
} else {
var idw = off >> 2;
var nShift = (off & 0x3) << 3;
this.adw[idw] = (this.adw[idw] & ~(0xff << nShift)) | (b << nShift);
}
this.fDirty = true;
},
/**
* writeShortMemory(off, w, addr)
*
* @this {Memory}
* @param {number} off
* @param {number} w
* @param {number} addr
*/
writeShortMemory: function writeShortMemory(off, w, addr) {
if (BYTEARRAYS) {
this.ab[off] = (w & 0xff);
this.ab[off + 1] = (w >> 8);
} else {
var idw = off >> 2;
var nShift = (off & 0x3) << 3;
if (nShift < 24) {
this.adw[idw] = (this.adw[idw] & ~(0xffff << nShift)) | (w << nShift);
} else {
this.adw[idw] = (this.adw[idw] & 0x00ffffff) | (w << 24);
idw++;
this.adw[idw] = (this.adw[idw] & (0xffffff00|0)) | (w >> 8);
}
}
this.fDirty = true;
},
/**
* readByteChecked(off, addr)
*
* @this {Memory}
* @param {number} off
* @param {number} addr
* @return {number}
*/
readByteChecked: function readByteChecked(off, addr) {
if (DEBUGGER && this.dbg && this.addr != null) {
this.dbg.checkMemoryRead(this.addr + off);
}
return this.readByteDirect(off, addr);
},
/**
* readShortChecked(off, addr)
*
* @this {Memory}
* @param {number} off
* @param {number} addr
* @return {number}
*/
readShortChecked: function readShortChecked(off, addr) {
if (DEBUGGER && this.dbg && this.addr != null) {
this.dbg.checkMemoryRead(this.addr + off, 2);
}
return this.readShortDirect(off, addr);
},
/**
* writeByteChecked(off, b, addr)
*
* @this {Memory}
* @param {number} off
* @param {number} addr
* @param {number} b
*/
writeByteChecked: function writeByteChecked(off, b, addr) {
if (DEBUGGER && this.dbg && this.addr != null) {
this.dbg.checkMemoryWrite(this.addr + off);
}
if (this.fReadOnly) this.writeNone(off, b, addr); else this.writeByteDirect(off, b, addr);
},
/**
* writeShortChecked(off, w, addr)
*
* @this {Memory}
* @param {number} off
* @param {number} addr
* @param {number} w
*/
writeShortChecked: function writeShortChecked(off, w, addr) {
if (DEBUGGER && this.dbg && this.addr != null) {
this.dbg.checkMemoryWrite(this.addr + off, 2)
}
if (this.fReadOnly) this.writeNone(off, w, addr); else this.writeShortDirect(off, w, addr);
},
/**
* readByteBE(off, addr)
*
* @this {Memory}
* @param {number} off
* @param {number} addr
* @return {number}
*/
readByteBE: function readByteBE(off, addr) {
return this.ab[off];
},
/**
* readByteLE(off, addr)
*
* @this {Memory}
* @param {number} off
* @param {number} addr
* @return {number}
*/
readByteLE: function readByteLE(off, addr) {
return this.ab[off];
},
/**
* readShortBE(off, addr)
*
* @this {Memory}
* @param {number} off
* @param {number} addr
* @return {number}
*/
readShortBE: function readShortBE(off, addr) {
return this.dv.getUint16(off, true);
},
/**
* readShortLE(off, addr)
*
* @this {Memory}
* @param {number} off
* @param {number} addr
* @return {number}
*/
readShortLE: function readShortLE(off, addr) {
/*
* TODO: It remains to be seen if there's any advantage to checking the offset for an aligned read
* vs. always reading the bytes separately; it seems a safe bet for longs, but it's less clear for shorts.
*/
return (off & 0x1)? (this.ab[off] | (this.ab[off+1] << 8)) : this.aw[off >> 1];
},
/**
* writeByteBE(off, b, addr)
*
* @this {Memory}
* @param {number} off
* @param {number} b
* @param {number} addr
*/
writeByteBE: function writeByteBE(off, b, addr) {
this.ab[off] = b;
this.fDirty = true;
},
/**
* writeByteLE(off, b, addr)
*
* @this {Memory}
* @param {number} off
* @param {number} addr
* @param {number} b
*/
writeByteLE: function writeByteLE(off, b, addr) {
this.ab[off] = b;
this.fDirty = true;
},
/**
* writeShortBE(off, w, addr)
*
* @this {Memory}
* @param {number} off
* @param {number} addr
* @param {number} w
*/
writeShortBE: function writeShortBE(off, w, addr) {
this.dv.setUint16(off, w, true);
this.fDirty = true;
},
/**
* writeShortLE(off, w, addr)
*
* @this {Memory}
* @param {number} off
* @param {number} addr
* @param {number} w
*/
writeShortLE: function writeShortLE(off, w, addr) {
/*
* TODO: It remains to be seen if there's any advantage to checking the offset for an aligned write
* vs. always writing the bytes separately; it seems a safe bet for longs, but it's less clear for shorts.
*/
if (off & 0x1) {
this.ab[off] = w;
this.ab[off+1] = w >> 8;
} else {
this.aw[off >> 1] = w;
}
this.fDirty = true;
}
};
/*
* This is the effective definition of afnNone, but we need not fully define it, because setAccess()
* uses these defaults when any of the 6 handlers (ie, 3 read handlers and 3 write handlers) are undefined.
*
Memory.afnNone = [Memory.prototype.readNone, Memory.prototype.readShortDefault, Memory.prototype.writeNone, Memory.prototype.writeShortDefault];
*/
Memory.afnNone = [];
Memory.afnMemory = [Memory.prototype.readByteMemory, Memory.prototype.readShortMemory, Memory.prototype.writeByteMemory, Memory.prototype.writeShortMemory];
Memory.afnChecked = [Memory.prototype.readByteChecked, Memory.prototype.readShortChecked, Memory.prototype.writeByteChecked, Memory.prototype.writeShortChecked];
if (TYPEDARRAYS) {
Memory.afnArrayBE = [Memory.prototype.readByteBE, Memory.prototype.readShortBE, Memory.prototype.writeByteBE, Memory.prototype.writeShortBE];
Memory.afnArrayLE = [Memory.prototype.readByteLE, Memory.prototype.readShortLE, Memory.prototype.writeByteLE, Memory.prototype.writeShortLE];
}
if (NODE) module.exports = Memory;

View file

@ -0,0 +1,54 @@
/**
* @fileoverview PC8080-specific message definitions.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2016-Apr-18
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <http://pcjs.org/>.
*
* PCjs 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 3
* of the License, or (at your option) any later version.
*
* PCjs 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 PCjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.COPYRIGHT).
*
* Some PCjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* PCjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
var Messages = {
CPU: 0x00000001,
BUS: 0x00000040,
MEM: 0x00000080,
PORT: 0x00000100,
TIMER: 0x00000800,
KEYBOARD: 0x00010000,
KEYS: 0x00020000,
VIDEO: 0x00040000,
FDC: 0x00080000,
DISK: 0x00200000,
SERIAL: 0x00800000,
SPEAKER: 0x02000000,
COMPUTER: 0x04000000,
LOG: 0x20000000,
WARN: 0x40000000,
HALT: 0x80000000|0
};
if (NODE) module.exports = Messages;

185
modules/pc8080/lib/panel.js Normal file
View file

@ -0,0 +1,185 @@
/**
* @fileoverview Implements the PC8080 Panel component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2016-Apr-19
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <http://pcjs.org/>.
*
* PCjs 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 3
* of the License, or (at your option) any later version.
*
* PCjs 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 PCjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.COPYRIGHT).
*
* Some PCjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* PCjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
if (NODE) {
var str = require("../../shared/lib/strlib");
var usr = require("../../shared/lib/usrlib");
var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var Bus = require("./bus");
var Memory = require("./memory");
var CPUDef = require("./cpudef");
}
/**
* Panel(parmsPanel)
*
* The Panel component has no required (parmsPanel) properties.
*
* @constructor
* @extends Component
* @param {Object} parmsPanel
*/
function Panel(parmsPanel)
{
Component.call(this, "Panel", parmsPanel, Panel);
}
Component.subclass(Panel);
/**
* setBinding(sHTMLType, sBinding, control, sValue)
*
* Most panel layouts don't have bindings of their own, so we pass along all binding requests to the
* Computer, CPU, Keyboard and Debugger components first. The order shouldn't matter, since any component
* that doesn't recognize the specified binding should simply ignore it.
*
* @this {Panel}
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas")
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "reset")
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
* @param {string} [sValue] optional data value
* @return {boolean} true if binding was successful, false if unrecognized binding request
*/
Panel.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
{
if (this.cmp && this.cmp.setBinding(sHTMLType, sBinding, control, sValue)) return true;
if (this.cpu && this.cpu.setBinding(sHTMLType, sBinding, control, sValue)) return true;
if (this.kbd && this.kbd.setBinding(sHTMLType, sBinding, control, sValue)) return true;
if (DEBUGGER && this.dbg && this.dbg.setBinding(sHTMLType, sBinding, control, sValue)) return true;
return this.parent.setBinding.call(this, sHTMLType, sBinding, control, sValue);
};
/**
* initBus(cmp, bus, cpu, dbg)
*
* @this {Panel}
* @param {Computer} cmp
* @param {Bus} bus
* @param {CPUSim} cpu
* @param {Debugger} dbg
*/
Panel.prototype.initBus = function(cmp, bus, cpu, dbg)
{
this.cmp = cmp;
this.bus = bus;
this.cpu = cpu;
this.dbg = dbg;
this.kbd = cmp.getMachineComponent("Keyboard");
};
/**
* powerUp(data, fRepower)
*
* @this {Panel}
* @param {Object|null} data
* @param {boolean} [fRepower]
* @return {boolean} true if successful, false if failure
*/
Panel.prototype.powerUp = function(data, fRepower)
{
if (!fRepower) Panel.init();
return true;
};
/**
* powerDown(fSave, fShutdown)
*
* @this {Panel}
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
*/
Panel.prototype.powerDown = function(fSave, fShutdown)
{
return true;
};
/**
* updateStatus(fForce)
*
* Update function for Panels containing elements with high-frequency display requirements.
*
* For older (and slower) DOM-based display elements, those are sill being managed by the CPUSim component,
* so it has its own updateStatus() handler.
*
* The Computer's updateStatus() handler is currently responsible for calling both our handler and the CPU's handler.
*
* @this {Panel}
* @param {boolean} [fForce] (true will display registers even if the CPU is running and "live" registers are not enabled)
*/
Panel.prototype.updateStatus = function(fForce)
{
};
/**
* Panel.init()
*
* This function operates on every HTML element of class "panel", extracting the
* JSON-encoded parameters for the Panel constructor from the element's "data-value"
* attribute, invoking the constructor to create a Panel component, and then binding
* any associated HTML controls to the new component.
*
* NOTE: Unlike most other component init() functions, this one is designed to be
* called multiple times: once at load time, so that we can binding our print()
* function to the panel's output control ASAP, and again when the Computer component
* is verifying that all components are ready and invoking their powerUp() functions.
*
* Our powerUp() method gives us a second opportunity to notify any components that
* that might care (eg, CPU, Keyboard, and Debugger) that we have some controls they
* might want to use.
*/
Panel.init = function()
{
var fReady = false;
var aePanels = Component.getElementsByClass(document, PCJSCLASS, "panel");
for (var iPanel=0; iPanel < aePanels.length; iPanel++) {
var ePanel = aePanels[iPanel];
var parmsPanel = Component.getComponentParms(ePanel);
var panel = Component.getComponentByID(parmsPanel['id']);
if (!panel) {
fReady = true;
panel = new Panel(parmsPanel);
}
Component.bindComponentControls(panel, ePanel, PCJSCLASS);
if (fReady) panel.setReady();
}
};
/*
* Initialize every Panel module on the page.
*/
web.onInit(Panel.init);
if (NODE) module.exports = Panel;

198
modules/pc8080/lib/ram.js Normal file
View file

@ -0,0 +1,198 @@
/**
* @fileoverview Implements the PC8080 RAM component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2016-Apr-19
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <http://pcjs.org/>.
*
* PCjs 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 3
* of the License, or (at your option) any later version.
*
* PCjs 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 PCjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.COPYRIGHT).
*
* Some PCjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* PCjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
if (NODE) {
var str = require("../../shared/lib/strlib");
var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var Memory = require("./memory");
var ROM = require("./rom");
var State = require("./state");
}
/**
* RAM(parmsRAM)
*
* The RAM component expects the following (parmsRAM) properties:
*
* addr: starting physical address of RAM (default is 0)
* size: amount of RAM, in bytes (default is 0, which means defer to motherboard switch settings)
*
* NOTE: We make a note of the specified size, but no memory is initially allocated for the RAM until the
* Computer component calls powerUp().
*
* @constructor
* @extends Component
* @param {Object} parmsRAM
*/
function RAM(parmsRAM)
{
Component.call(this, "RAM", parmsRAM, RAM);
this.addrRAM = parmsRAM['addr'];
this.sizeRAM = parmsRAM['size'];
this.fInstalled = (!!this.sizeRAM); // 0 is the default value for 'size' when none is specified
this.fAllocated = false;
}
Component.subclass(RAM);
/**
* initBus(cmp, bus, cpu, dbg)
*
* @this {RAM}
* @param {Computer} cmp
* @param {Bus} bus
* @param {CPUSim} cpu
* @param {Debugger} dbg
*/
RAM.prototype.initBus = function(cmp, bus, cpu, dbg)
{
this.bus = bus;
this.cpu = cpu;
this.dbg = dbg;
this.setReady();
};
/**
* powerUp(data, fRepower)
*
* @this {RAM}
* @param {Object|null} data
* @param {boolean} [fRepower]
* @return {boolean} true if successful, false if failure
*/
RAM.prototype.powerUp = function(data, fRepower)
{
if (!fRepower) {
/*
* The Computer powers up the CPU last, at which point CPUSim state is restored,
* which includes the Bus state, and since we use the Bus to allocate all our memory,
* memory contents are already restored for us, so we don't need the usual restore
* logic. We just need to call reset(), to allocate memory for the RAM.
*/
this.reset();
}
return true;
};
/**
* powerDown(fSave, fShutdown)
*
* @this {RAM}
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
*/
RAM.prototype.powerDown = function(fSave, fShutdown)
{
/*
* The Computer powers down the CPU first, at which point CPUSim state is saved,
* which includes the Bus state, and since we use the Bus component to allocate all
* our memory, memory contents are already saved for us, so we don't need the usual
* save logic.
*/
return (fSave)? this.save() : true;
};
/**
* reset()
*
* @this {RAM}
*/
RAM.prototype.reset = function()
{
if (!this.fAllocated && this.sizeRAM) {
if (this.bus.addMemory(this.addrRAM, this.sizeRAM, Memory.TYPE.RAM)) {
this.fAllocated = true;
this.status(Math.floor(this.sizeRAM / 1024) + "Kb allocated");
}
}
if (!this.fAllocated) {
Component.error("No RAM allocated");
}
};
/**
* save()
*
* This implements save support for the RAM component.
*
* @this {RAM}
* @return {Object}
*/
RAM.prototype.save = function()
{
return null;
};
/**
* restore(data)
*
* This implements restore support for the RAM component.
*
* @this {RAM}
* @param {Object} data
* @return {boolean} true if successful, false if failure
*/
RAM.prototype.restore = function(data)
{
return true;
};
/**
* RAM.init()
*
* This function operates on every HTML element of class "ram", extracting the
* JSON-encoded parameters for the RAM constructor from the element's "data-value"
* attribute, invoking the constructor to create a RAM component, and then binding
* any associated HTML controls to the new component.
*/
RAM.init = function()
{
var aeRAM = Component.getElementsByClass(document, PCJSCLASS, "ram");
for (var iRAM = 0; iRAM < aeRAM.length; iRAM++) {
var eRAM = aeRAM[iRAM];
var parmsRAM = Component.getComponentParms(eRAM);
var ram = new RAM(parmsRAM);
Component.bindComponentControls(ram, eRAM, PCJSCLASS);
}
};
/*
* Initialize all the RAM modules on the page.
*/
web.onInit(RAM.init);
if (NODE) module.exports = RAM;

374
modules/pc8080/lib/rom.js Normal file
View file

@ -0,0 +1,374 @@
/**
* @fileoverview Implements the PC8080 ROM component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2016-Apr-19
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <http://pcjs.org/>.
*
* PCjs 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 3
* of the License, or (at your option) any later version.
*
* PCjs 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 PCjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.COPYRIGHT).
*
* Some PCjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* PCjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
if (NODE) {
var str = require("../../shared/lib/strlib");
var web = require("../../shared/lib/weblib");
var DumpAPI = require("../../shared/lib/dumpapi");
var Component = require("../../shared/lib/component");
var Memory = require("./memory");
}
/**
* ROM(parmsROM)
*
* The ROM component expects the following (parmsROM) properties:
*
* addr: physical address of ROM
* size: amount of ROM, in bytes
* alias: physical alias address (null if none)
* file: name of ROM data file
* notify: ID of a component to notify once the ROM is in place (optional)
*
* NOTE: The ROM data will not be copied into place until the Bus is ready (see initBus()) AND the
* ROM data file has finished loading (see doneLoad()).
*
* Also, while the size parameter may seem redundant, I consider it useful to confirm that the ROM you received
* is the ROM you expected.
*
* @constructor
* @extends Component
* @param {Object} parmsROM
*/
function ROM(parmsROM)
{
Component.call(this, "ROM", parmsROM, ROM);
this.abROM = null;
this.addrROM = parmsROM['addr'];
this.sizeROM = parmsROM['size'];
/*
* The new 'alias' property can now be EITHER a single physical address (like 'addr') OR an array of
* physical addresses; eg:
*
* [0xf0000,0xffff0000,0xffff8000]
*
* We could have overloaded 'addr' to accomplish the same thing, but I think it's better to have any
* aliased locations listed under a separate property.
*
* Most ROMs are not aliased, in which case the 'alias' property should have the default value of null.
*/
this.addrAlias = parmsROM['alias'];
this.sFilePath = parmsROM['file'];
this.sFileName = str.getBaseName(this.sFilePath);
if (this.sFilePath) {
var sFileURL = this.sFilePath;
if (DEBUG) this.log('load("' + sFileURL + '")');
/*
* If the selected ROM file has a ".json" extension, then we assume it's pre-converted
* JSON-encoded ROM data, so we load it as-is; ditto for ROM files with a ".hex" extension.
* Otherwise, we ask our server-side ROM converter to return the file in a JSON-compatible format.
*/
var sFileExt = str.getExtension(this.sFileName);
if (sFileExt != DumpAPI.FORMAT.JSON && sFileExt != DumpAPI.FORMAT.HEX) {
sFileURL = web.getHost() + DumpAPI.ENDPOINT + '?' + DumpAPI.QUERY.FILE + '=' + this.sFilePath + '&' + DumpAPI.QUERY.FORMAT + '=' + DumpAPI.FORMAT.BYTES + '&' + DumpAPI.QUERY.DECIMAL + '=true';
}
var rom = this;
web.getResource(sFileURL, null, true, function(sURL, sResponse, nErrorCode) {
rom.doneLoad(sURL, sResponse, nErrorCode);
});
}
}
Component.subclass(ROM);
/*
* NOTE: There's currently no need for this component to have a reset() function, since
* once the ROM data is loaded, it can't be changed, so there's nothing to reinitialize.
*
* OK, well, I take that back, because the Debugger, if installed, has the ability to modify
* ROM contents, so in that case, having a reset() function that restores the original ROM data
* might be useful; then again, it might not, depending on what you're trying to debug.
*
* If we do add reset(), then we'll want to change copyROM() to hang onto the original
* ROM data; currently, we release it after copying it into the read-only memory allocated
* via bus.addMemory().
*/
/**
* initBus(cmp, bus, cpu, dbg)
*
* @this {ROM}
* @param {Computer} cmp
* @param {Bus} bus
* @param {CPUSim} cpu
* @param {Debugger} dbg
*/
ROM.prototype.initBus = function(cmp, bus, cpu, dbg)
{
this.bus = bus;
this.cpu = cpu;
this.dbg = dbg;
this.copyROM();
};
/**
* powerUp(data, fRepower)
*
* @this {ROM}
* @param {Object|null} data
* @param {boolean} [fRepower]
* @return {boolean} true if successful, false if failure
*/
ROM.prototype.powerUp = function(data, fRepower)
{
if (this.aSymbols) {
if (this.dbg) {
this.dbg.addSymbols(this.id, this.addrROM, this.sizeROM, this.aSymbols);
}
/*
* Our only role in the handling of symbols is to hand them off to the Debugger at our
* first opportunity. Now that we've done that, our copy of the symbols, if any, are toast.
*/
delete this.aSymbols;
}
return true;
};
/**
* powerDown(fSave, fShutdown)
*
* Since we have nothing to do on powerDown(), and no state to return, we could simply omit
* this function. But it doesn't hurt anything, and maybe we'll use our state to save something
* useful down the road, like user-defined symbols (ie, symbols that the Debugger may have
* created, above and beyond those symbols we automatically loaded, if any, along with the ROM).
*
* @this {ROM}
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
*/
ROM.prototype.powerDown = function(fSave, fShutdown)
{
return true;
};
/**
* doneLoad(sURL, sROMData, nErrorCode)
*
* @this {ROM}
* @param {string} sURL
* @param {string} sROMData
* @param {number} nErrorCode (response from server if anything other than 200)
*/
ROM.prototype.doneLoad = function(sURL, sROMData, nErrorCode)
{
if (nErrorCode) {
this.notice("Unable to load system ROM (error " + nErrorCode + ": " + sURL + ")");
return;
}
Component.addMachineResource(this.idMachine, sURL, sROMData);
if (sROMData.charAt(0) == "[" || sROMData.charAt(0) == "{") {
try {
/*
* The most likely source of any exception will be here: parsing the JSON-encoded ROM data.
*/
var rom = eval("(" + sROMData + ")");
var ab = rom['bytes'];
var adw = rom['data'];
if (ab) {
this.abROM = ab;
}
else if (adw) {
/*
* Convert all the DWORDs into BYTEs, so that subsequent code only has to deal with abROM.
*/
this.abROM = new Array(adw.length * 4);
for (var idw = 0, ib = 0; idw < adw.length; idw++) {
this.abROM[ib++] = adw[idw] & 0xff;
this.abROM[ib++] = (adw[idw] >> 8) & 0xff;
this.abROM[ib++] = (adw[idw] >> 16) & 0xff;
this.abROM[ib++] = (adw[idw] >> 24) & 0xff;
}
}
else {
this.abROM = rom;
}
this.aSymbols = rom['symbols'];
if (!this.abROM.length) {
Component.error("Empty ROM: " + sURL);
return;
}
else if (this.abROM.length == 1) {
Component.error(this.abROM[0]);
return;
}
} catch (e) {
this.notice("ROM data error: " + e.message);
return;
}
}
else {
/*
* Parse the ROM data manually; we assume it's in "simplified" hex form (a series of hex byte-values
* separated by whitespace).
*/
var sHexData = sROMData.replace(/\n/gm, " ").replace(/ +$/, "");
var asHexData = sHexData.split(" ");
this.abROM = new Array(asHexData.length);
for (var i = 0; i < asHexData.length; i++) {
this.abROM[i] = str.parseInt(asHexData[i], 16);
}
}
this.copyROM();
};
/**
* copyROM()
*
* This function is called by both initBus() and doneLoad(), but it cannot copy the the ROM data into place
* until after initBus() has received the Bus component AND doneLoad() has received the abROM data. When both
* those criteria are satisfied, the component becomes "ready".
*
* @this {ROM}
*/
ROM.prototype.copyROM = function()
{
if (!this.isReady()) {
if (!this.sFilePath) {
this.setReady();
}
else if (this.abROM && this.bus) {
if (this.abROM.length != this.sizeROM) {
/*
* Note that setError() sets the component's fError flag, which in turn prevents setReady() from
* marking the component ready. TODO: Revisit this decision. On the one hand, it sounds like a
* good idea to stop the machine in its tracks whenever a setError() occurs, but there may also be
* times when we'd like to forge ahead anyway.
*/
this.setError("ROM size (" + str.toHexLong(this.abROM.length) + ") does not match specified size (" + str.toHexLong(this.sizeROM) + ")");
}
else if (this.addROM(this.addrROM)) {
var aliases = [];
if (typeof this.addrAlias == "number") {
aliases.push(this.addrAlias);
} else if (this.addrAlias != null && this.addrAlias.length) {
aliases = this.addrAlias;
}
for (var i = 0; i < aliases.length; i++) {
this.cloneROM(aliases[i]);
}
/*
* We used to hang onto the original ROM data so that we could restore any bytes the CPU overwrote,
* using memory write-notification handlers, but with the introduction of read-only memory blocks, that's
* no longer necessary.
*
* TODO: Consider an option to retain the ROM data, and give the user some way of restoring ROMs.
* That may be useful for "resumable" machines that save/restore all dirty block of memory, regardless
* whether they're ROM or RAM. However, the only way to modify a machine's ROM is with the Debugger,
* and Debugger users should know better.
*/
delete this.abROM;
}
this.setReady();
}
}
};
/**
* addROM(addr)
*
* @this {ROM}
* @param {number} addr
* @return {boolean}
*/
ROM.prototype.addROM = function(addr)
{
if (this.bus.addMemory(addr, this.sizeROM, Memory.TYPE.ROM)) {
if (DEBUG) this.log("addROM(): copying ROM to " + str.toHexLong(addr) + " (" + str.toHexLong(this.abROM.length) + " bytes)");
var bto = null;
for (var off = 0; off < this.abROM.length; off++) {
this.bus.setByteDirect(addr + off, this.abROM[off]);
}
return true;
}
/*
* We don't need to report an error here, because addMemory() already takes care of that.
*/
return false;
};
/**
* cloneROM(addr)
*
* For ROMs with one or more alias addresses, we used to call addROM() for each address. However,
* that obviously wasted memory, since each alias was an independent copy, and if you used the
* Debugger to edit the ROM in one location, the changes would not appear in the other location(s).
*
* Now that the Bus component provides low-level getMemoryBlocks() and setMemoryBlocks() methods
* to manually get and set the blocks of any memory range, it is now possible to create true aliases.
*
* @this {ROM}
* @param {number} addr
*/
ROM.prototype.cloneROM = function(addr)
{
var aBlocks = this.bus.getMemoryBlocks(this.addrROM, this.sizeROM);
this.bus.setMemoryBlocks(addr, this.sizeROM, aBlocks);
};
/**
* ROM.init()
*
* This function operates on every HTML element of class "rom", extracting the
* JSON-encoded parameters for the ROM constructor from the element's "data-value"
* attribute, invoking the constructor to create a ROM component, and then binding
* any associated HTML controls to the new component.
*/
ROM.init = function()
{
var aeROM = Component.getElementsByClass(document, PCJSCLASS, "rom");
for (var iROM = 0; iROM < aeROM.length; iROM++) {
var eROM = aeROM[iROM];
var parmsROM = Component.getComponentParms(eROM);
var rom = new ROM(parmsROM);
Component.bindComponentControls(rom, eROM, PCJSCLASS);
}
};
/*
* Initialize all the ROM modules on the page.
*/
web.onInit(ROM.init);
if (NODE) module.exports = ROM;

397
modules/pc8080/lib/state.js Normal file
View file

@ -0,0 +1,397 @@
/**
* @fileoverview The State class used by PCjs machines.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2016-Apr-19
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <http://pcjs.org/>.
*
* PCjs 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 3
* of the License, or (at your option) any later version.
*
* PCjs 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 PCjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.COPYRIGHT).
*
* Some PCjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* PCjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
if (NODE) {
var web = require("./../../shared/lib/weblib");
var Component = require("./../../shared/lib/component");
var Messages = require("./messages");
}
/**
* State(component, sVersion, sSuffix)
*
* State objects are used by components to save/restore their state.
*
* During a save operation, components add data to a State object via set(),
* and then return the resulting data using data().
*
* During a restore operation, the Computer component passes the results of each
* data() call back to the originating component.
*
* WARNING: Since State objects are low-level objects that have no UI requirements,
* they do not inherit from the Component class, so you should only use class methods
* of Component, such as Component.assert(), or Debugger methods if the Debugger
* is available.
*
* @constructor
* @param {Component} component
* @param {string} [sVersion] is used to append a major version number to the key
* @param {string} [sSuffix] is used to append any additional suffixes to the key
*/
function State(component, sVersion, sSuffix) {
this.id = component.id;
this.key = State.key(component, sVersion, sSuffix);
this.dbg = component.dbg;
this.unload(component.parms);
}
/**
* State.key(component, sVersion, sSuffix)
*
* This encapsulates the key generation code.
*
* @param {Component} component
* @param {string} [sVersion] is used to append a major version number to the key
* @param {string} [sSuffix] is used to append any additional suffixes to the key
* @return {string} key
*/
State.key = function(component, sVersion, sSuffix) {
var key = component.id;
if (sVersion) {
var i = sVersion.indexOf('.');
if (i > 0) key += ".v" + sVersion.substr(0, i);
}
if (sSuffix) {
key += "." + sSuffix;
}
return key;
};
/**
* State.compress(aSrc)
*
* @param {Array.<number>|null} aSrc
* @return {Array.<number>|null} is either the original array (aSrc), or a smaller array of "count, value" pairs (aComp)
*/
State.compress = function(aSrc) {
if (aSrc) {
var iSrc = 0;
var iComp = 0;
var aComp = [];
while (iSrc < aSrc.length) {
var n = aSrc[iSrc];
Component.assert(n !== undefined);
var iCompare = iSrc + 1;
while (iCompare < aSrc.length && aSrc[iCompare] === n) iCompare++;
aComp[iComp++] = iCompare - iSrc;
aComp[iComp++] = n;
iSrc = iCompare;
}
if (aComp.length < aSrc.length) return aComp;
}
return aSrc;
};
/**
* State.decompress(aComp)
*
* @param {Array.<number>} aComp
* @param {number} nLength is expected length of decompressed data
* @return {Array.<number>}
*/
State.decompress = function(aComp, nLength) {
var iDst = 0;
var aDst = new Array(nLength);
var iComp = 0;
while (iComp < aComp.length - 1) {
var c = aComp[iComp++];
var n = aComp[iComp++];
while (c--) {
aDst[iDst++] = n;
}
}
Component.assert(aDst.length == nLength);
return aDst;
};
/**
* State.compressEvenOdd(aSrc)
*
* This is a very simple variation on compress() that compresses all the EVEN elements of aSrc first,
* followed by all the ODD elements. This tends to work better on EGA video memory, because when odd/even
* addressing is enabled (eg, for text modes), the DWORD values tend to alternate, which is the worst case
* for compress(), but the best case for compressEvenOdd().
*
* One wrinkle we support: if the first element is uninitialized, then we assume the entire array is undefined,
* and return an empty compressed array. Conversely, decompressEvenOdd() will take an empty compressed array
* and return an uninitialized array.
*
* @param {Array.<number>|null} aSrc
* @return {Array.<number>|null} is either the original array (aSrc), or a smaller array of "count, value" pairs (aComp)
*/
State.compressEvenOdd = function(aSrc) {
if (aSrc) {
var iComp = 0, aComp = [];
if (aSrc[0] !== undefined) {
for (var off = 0; off < 2; off++) {
var iSrc = off;
while (iSrc < aSrc.length) {
var n = aSrc[iSrc];
var iCompare = iSrc + 2;
while (iCompare < aSrc.length && aSrc[iCompare] === n) iCompare += 2;
aComp[iComp++] = (iCompare - iSrc) >> 1;
aComp[iComp++] = n;
iSrc = iCompare;
}
}
}
if (aComp.length < aSrc.length) return aComp;
}
return aSrc;
};
/**
* State.decompressEvenOdd(aComp, nLength)
*
* This is the counterpart to compressEvenOdd(). Note that because there's nothing in the compressed sequence
* that differentiates a compress() sequence from a compressEvenOdd() sequence, you simply have to be consistent:
* if you used even/odd compression, then you must use even/odd decompression.
*
* @param {Array.<number>} aComp
* @param {number} nLength is expected length of decompressed data
* @return {Array.<number>}
*/
State.decompressEvenOdd = function(aComp, nLength) {
var iDst = 0;
var aDst = new Array(nLength);
var iComp = 0;
while (iComp < aComp.length - 1) {
var c = aComp[iComp++];
var n = aComp[iComp++];
while (c--) {
aDst[iDst] = n;
iDst += 2;
}
/*
* The output of a "count,value" pair will never exceed the end of the output array, so as soon as we reach it
* the first time, we know it's time to switch to ODD elements, and as soon as we reach it again, we should be
* done.
*/
Component.assert(iDst <= nLength || iComp == aComp.length);
if (iDst == nLength) iDst = 1;
}
Component.assert(aDst.length == nLength);
return aDst;
};
State.prototype = {
constructor: State,
/**
* set(id, data)
*
* @this {State}
* @param {number|string} id
* @param {Object|string} data
*/
set: function(id, data) {
try {
this[this.id][id] = data;
} catch(e) {
Component.log(e.message);
}
},
/**
* get(id)
*
* @this {State}
* @param {number|string} id
* @return {Object|string|null}
*/
get: function(id) {
return this[this.id][id] || null;
},
/**
* value()
*
* Use this instead of data() if you haven't called parse() yet.
*
* @this {State}
* @return {string}
*/
value: function() {
return this[this.id];
},
/**
* data()
*
* @this {State}
* @return {Object}
*/
data: function() {
return this[this.id];
},
/**
* load(s)
*
* WARNING: Make sure you follow this call with either a call to parse() or unload(),
* because any stringified data that we've loaded isn't usable until it's been parsed.
*
* @this {State}
* @param {Object|string|null} [s]
* @return {boolean} true if state exists in localStorage, false if not
*/
load: function(s) {
if (s) {
this[this.id] = s;
this.fLoaded = true;
return true;
}
if (this.fLoaded) {
/*
* This is assumed to be a redundant load().
*/
return true;
}
if (web.hasLocalStorage()) {
s = web.getLocalStorageItem(this.key);
if (s) {
this[this.id] = s;
this.fLoaded = true;
if (DEBUG) this.printString("localStorage(" + this.key + "): " + s.length + " bytes loaded");
return true;
}
}
return false;
},
/**
* parse()
*
* This completes the load() operation, by parsing what was loaded, on the assumption there
* might be some benefit to deferring parsing until we've given the user a chance to confirm.
* Otherwise, load() could have just as easily done this, too.
*
* @this {State}
* @return {boolean} true if successful, false if error
*/
parse: function() {
var fSuccess = true;
try {
this[this.id] = JSON.parse(this[this.id]);
} catch (e) {
Component.error(e.message || e);
fSuccess = false;
}
return fSuccess;
},
/**
* store()
*
* @this {State}
* @return {boolean} true if successful, false if error
*/
store: function() {
var fSuccess = true;
if (web.hasLocalStorage()) {
var s = JSON.stringify(this[this.id]);
if (web.setLocalStorageItem(this.key, s)) {
if (DEBUG) this.printString("localStorage(" + this.key + "): " + s.length + " bytes stored");
} else {
/*
* WARNING: Because browsers tend to disable all alerts() during an "unload" operation,
* it's unlikely anyone will ever see the "quota" errors that occur at this point. Need to
* think of some way to notify the user that there's a problem, and offer a way of cleaning
* up old states.
*/
Component.error("Unable to store " + s.length + " bytes in browser local storage");
fSuccess = false;
}
}
return fSuccess;
},
/**
* toString()
*
* We can't know whether this might be called before parse() or after parse(), so we check.
* If before, then this[this.id] will still be in string form; if after, it will be an Object.
*
* @this {State}
* @return {string} JSON-encoded state
*/
toString: function() {
var value = this[this.id];
return (typeof value == "string"? value : JSON.stringify(value));
},
/**
* unload(parms)
*
* This discards any data saved via set() or loaded via load(), creating an empty State object.
* Note that you have to follow this call with an explicit call to store() if you want to remove
* the state from localStorage as well.
*
* @this {State}
* @param {Object} [parms]
*/
unload: function(parms) {
this[this.id] = {};
if (parms) this.set("parms", parms);
this.fLoaded = false;
},
/**
* clear(fAll)
*
* This unloads the current state, and then clears ALL localStorage for the current machine,
* independent of version, to reduce the chance of orphaned states wasting part of our limited allocation.
*
* @this {State}
* @param {boolean} [fAll] true to unconditionally clear ALL localStorage for the current domain
*/
clear: function(fAll) {
this.unload();
var aKeys = web.getLocalStorageKeys();
for (var i = 0; i < aKeys.length; i++) {
var sKey = aKeys[i];
if (sKey && (fAll || sKey.substr(0, this.key.length) == this.key)) {
web.removeLocalStorageItem(sKey);
if (DEBUG) this.printString("localStorage(" + sKey + ") removed");
aKeys.splice(i, 1);
i = 0;
}
}
},
/**
* printString(s)
*
* @this {State}
* @param {string} s is any caller-defined string
*/
printString: function(s) {
if (DEBUG && DEBUGGER && this.dbg) {
if (this.dbg.messageEnabled(Messages.LOG)) {
this.dbg.message(s);
}
}
}
};
if (NODE) module.exports = State;

View file

@ -119,7 +119,7 @@ function Memory(addr, used, size, type, controller, cpu)
* which still use conventional memory blocks. Alternatively, we could restrict the use of dirty block tracking
* to certain memory types (eg, VIDEO memory).
*
* However, a quick test with with dirty block tracking disabled didn't yield a noticeable improvement in performance,
* However, a quick test with dirty block tracking disabled didn't yield a noticeable improvement in performance,
* so I think the overhead of our block-based architecture is swamping the impact of these micro-updates.
*/
this.fDirty = this.fDirtyEver = false;