Infrastructure in place for 8080 hardware interrupts (more specifically, vertical retrace interrupts)
This commit is contained in:
parent
2a45531c2a
commit
07259c93ef
17 changed files with 1072 additions and 434 deletions
486
modules/pc8080/lib/chipset.js
Normal file
486
modules/pc8080/lib/chipset.js
Normal file
|
|
@ -0,0 +1,486 @@
|
|||
/**
|
||||
* @fileoverview Implements the PC8080 ChipSet component.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2016-Apr-25
|
||||
*
|
||||
* 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 Messages = require("./messages");
|
||||
var State = require("./state");
|
||||
var CPUDef = require("./cpudef");
|
||||
}
|
||||
|
||||
/**
|
||||
* ChipSet(parmsChipSet)
|
||||
*
|
||||
* The ChipSet component has the following component-specific (parmsChipSet) properties:
|
||||
*
|
||||
* model: eg, "SI1978" (should be a member of ChipSet.MODELS)
|
||||
* swDIP: eg, "00000000", where swDIP[0] is DIP0, swDIP[1] is DIP1, etc.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
* @param {Object} parmsChipSet
|
||||
*/
|
||||
function ChipSet(parmsChipSet)
|
||||
{
|
||||
Component.call(this, "ChipSet", parmsChipSet, ChipSet, Messages.CHIPSET);
|
||||
|
||||
var model = parmsChipSet['model'];
|
||||
|
||||
/*
|
||||
* this.model is a numeric version of the 'model' string; when comparing this.model to standard IBM
|
||||
* model numbers, you should generally compare (this.model|0) to the target value, which truncates it.
|
||||
*/
|
||||
if (model && !ChipSet.MODELS[model]) {
|
||||
Component.notice("Unrecognized ChipSet model: " + model);
|
||||
}
|
||||
|
||||
this.model = ChipSet.MODELS[model];
|
||||
|
||||
this.bSwitches = this.parseDIPSwitches(parmsChipSet['swDIP']);
|
||||
|
||||
/*
|
||||
* Here, I'm finally getting around to trying the Web Audio API. Fortunately, based on what little I know about
|
||||
* sound generation, using the API to make the same noises as the IBM PC speaker seems straightforward.
|
||||
*
|
||||
* To start, we create an audio context, unless the 'sound' parameter has been explicitly set to false.
|
||||
*
|
||||
* From:
|
||||
*
|
||||
* http://developer.apple.com/library/safari/#documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/PlayingandSynthesizingSounds/PlayingandSynthesizingSounds.html
|
||||
*
|
||||
* "Similar to how HTML5 canvas requires a context on which lines and curves are drawn, Web Audio requires an audio context
|
||||
* on which sounds are played and manipulated. This context will be the parent object of further audio objects to come....
|
||||
* Your audio context is typically created when your page initializes and should be long-lived. You can play multiple sounds
|
||||
* coming from multiple sources within the same context, so it is unnecessary to create more than one audio context per page."
|
||||
*/
|
||||
this.fSpeaker = false;
|
||||
if (parmsChipSet['sound']) {
|
||||
this.classAudio = this.contextAudio = null;
|
||||
if (window) {
|
||||
this.classAudio = window['AudioContext'] || window['webkitAudioContext'];
|
||||
}
|
||||
if (this.classAudio) {
|
||||
this.contextAudio = new this.classAudio();
|
||||
} else {
|
||||
if (DEBUG) this.log("AudioContext not available");
|
||||
}
|
||||
}
|
||||
|
||||
this.setReady();
|
||||
}
|
||||
|
||||
Component.subclass(ChipSet);
|
||||
|
||||
ChipSet.SI_1978 = {
|
||||
MODEL: 1978.1,
|
||||
STATUS0: {
|
||||
PORT: 0,
|
||||
DIP4: 0x01, // self-test request at power up?
|
||||
ALWAYS_SET: 0x0E, // always set
|
||||
FIRE: 0x10, // fire
|
||||
LEFT: 0x20, // move left
|
||||
RIGHT: 0x40, // move right
|
||||
PORT7: 0x80 // some connection to port 7?
|
||||
},
|
||||
STATUS1: {
|
||||
PORT: 1,
|
||||
CREDIT: 0x01, // credit
|
||||
P1: 0x02, // 1P start
|
||||
P2: 0x04, // 2P start
|
||||
ALWAYS_SET: 0x08, // always set
|
||||
P1_FIRE: 0x10, // P1 fire (cocktail machines only?)
|
||||
P1_LEFT: 0x20, // P1 left (cocktail machines only?)
|
||||
P1_RIGHT: 0x40 // P1 right (cocktail machines only?)
|
||||
},
|
||||
STATUS2: {
|
||||
PORT: 2,
|
||||
DIP3_5: 0x03, // 00 = 3 ships, 01 = 4 ships, 10 = 5 ships, 11 = 6 ships
|
||||
TILT: 0x04,
|
||||
DIP6: 0x08, // 0 = extra ship at 1500, 1 = extra ship at 1000
|
||||
P2_FIRE: 0x10, // P2 fire (cocktail machines only?)
|
||||
P2_LEFT: 0x20, // P2 left (cocktail machines only?)
|
||||
P2_RIGHT: 0x40, // P2 right (cocktail machines only?)
|
||||
DIP7: 0x80 // 0 = display coin info on demo ("attract") screen
|
||||
},
|
||||
SHIFT_RESULT: { // bits 0-7 of barrel shifter result
|
||||
PORT: 3
|
||||
},
|
||||
SHIFT_COUNT: {
|
||||
PORT: 2,
|
||||
MASK: 0x07
|
||||
},
|
||||
SOUND1: {
|
||||
PORT: 3,
|
||||
UFO: 0x01,
|
||||
SHOT: 0x02,
|
||||
PDEATH: 0x04,
|
||||
IDEATH: 0x08,
|
||||
EXPLAY: 0x10,
|
||||
AMP_ENABLE: 0x20
|
||||
},
|
||||
SHIFT_DATA: {
|
||||
PORT: 4
|
||||
},
|
||||
SOUND2: {
|
||||
PORT: 5,
|
||||
FLEET1: 0x01,
|
||||
FLEET2: 0x02,
|
||||
FLEET3: 0x04,
|
||||
FLEET4: 0x08,
|
||||
UFO_HIT: 0x10
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Supported model strings
|
||||
*/
|
||||
ChipSet.MODELS = {
|
||||
"SI1978": ChipSet.SI_1978.MODEL
|
||||
};
|
||||
|
||||
/**
|
||||
* parseDIPSwitches(sBits, bDefault)
|
||||
*
|
||||
* @this {ChipSet}
|
||||
* @param {string} sBits describing switch settings
|
||||
* @param {number} [bDefault]
|
||||
* @return {number|undefined}
|
||||
*/
|
||||
ChipSet.prototype.parseDIPSwitches = function(sBits, bDefault)
|
||||
{
|
||||
var b = bDefault;
|
||||
if (sBits) {
|
||||
/*
|
||||
* NOTE: We can't use parseInt() with a base of 2, because both bit order and bit sense are reversed.
|
||||
*/
|
||||
b = 0;
|
||||
var bit = 0x1;
|
||||
for (var i = 0; i < sBits.length; i++) {
|
||||
if (sBits.charAt(i) == "0") b |= bit;
|
||||
bit <<= 1;
|
||||
}
|
||||
}
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* setBinding(sHTMLType, sBinding, control, sValue)
|
||||
*
|
||||
* @this {ChipSet}
|
||||
* @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, "sw1")
|
||||
* @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
|
||||
*/
|
||||
ChipSet.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
|
||||
{
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* initBus(cmp, bus, cpu, dbg)
|
||||
*
|
||||
* @this {ChipSet}
|
||||
* @param {Computer} cmp
|
||||
* @param {Bus} bus
|
||||
* @param {CPUSim} cpu
|
||||
* @param {Debugger} dbg
|
||||
*/
|
||||
ChipSet.prototype.initBus = function(cmp, bus, cpu, dbg)
|
||||
{
|
||||
this.bus = bus;
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
this.cmp = cmp;
|
||||
if (this.model == ChipSet.SI_1978.MODEL) {
|
||||
bus.addPortInputTable(this, ChipSet.aPortInput);
|
||||
bus.addPortOutputTable(this, ChipSet.aPortOutput);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* powerUp(data, fRepower)
|
||||
*
|
||||
* @this {ChipSet}
|
||||
* @param {Object|null} data
|
||||
* @param {boolean} [fRepower]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
ChipSet.prototype.powerUp = function(data, fRepower)
|
||||
{
|
||||
if (!fRepower) {
|
||||
if (!data) {
|
||||
this.reset();
|
||||
} else {
|
||||
if (!this.restore(data)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* powerDown(fSave, fShutdown)
|
||||
*
|
||||
* @this {ChipSet}
|
||||
* @param {boolean} [fSave]
|
||||
* @param {boolean} [fShutdown]
|
||||
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
|
||||
*/
|
||||
ChipSet.prototype.powerDown = function(fSave, fShutdown)
|
||||
{
|
||||
return fSave? this.save() : true;
|
||||
};
|
||||
|
||||
/**
|
||||
* reset()
|
||||
*
|
||||
* @this {ChipSet}
|
||||
*/
|
||||
ChipSet.prototype.reset = function()
|
||||
{
|
||||
this.bStatus0 = 0;
|
||||
this.bStatus1 = 0;
|
||||
this.bStatus2 = 0;
|
||||
this.bShiftData = 0;
|
||||
this.bShiftCount = 0;
|
||||
this.bSound1 = this.bSound2 = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* save()
|
||||
*
|
||||
* This implements save support for the ChipSet component.
|
||||
*
|
||||
* @this {ChipSet}
|
||||
* @return {Object}
|
||||
*/
|
||||
ChipSet.prototype.save = function()
|
||||
{
|
||||
var state = new State(this);
|
||||
if (this.model == ChipSet.SI_1978.MODEL) {
|
||||
state.set(0, [this.bStatus0, this.bStatus1, this.bStatus2, this.bShiftData, this.bShiftCount, this.bSound1, this.bSound2]);
|
||||
}
|
||||
return state.data();
|
||||
};
|
||||
|
||||
/**
|
||||
* restore(data)
|
||||
*
|
||||
* This implements restore support for the ChipSet component.
|
||||
*
|
||||
* @this {ChipSet}
|
||||
* @param {Object} data
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
ChipSet.prototype.restore = function(data)
|
||||
{
|
||||
var a, i;
|
||||
a = data[0];
|
||||
if (this.model == ChipSet.SI_1978.MODEL) {
|
||||
this.bStatus0 = a[0];
|
||||
this.bStatus1 = a[1];
|
||||
this.bStatus2 = a[2];
|
||||
this.bShiftData = a[3];
|
||||
this.bShiftCount = a[4];
|
||||
this.bSound1 = a[5];
|
||||
this.bSound2 = a[6];
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* inSIStatus0(port, addrFrom)
|
||||
*
|
||||
* @this {ChipSet}
|
||||
* @param {number} port (0x00)
|
||||
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
|
||||
* @return {number} simulated port value
|
||||
*/
|
||||
ChipSet.prototype.inSIStatus0 = function(port, addrFrom)
|
||||
{
|
||||
var b = this.bStatus0;
|
||||
this.printMessageIO(port, null, addrFrom, "STATUS0", b, true);
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* inSIStatus1(port, addrFrom)
|
||||
*
|
||||
* @this {ChipSet}
|
||||
* @param {number} port (0x01)
|
||||
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
|
||||
* @return {number} simulated port value
|
||||
*/
|
||||
ChipSet.prototype.inSIStatus1 = function(port, addrFrom)
|
||||
{
|
||||
var b = this.bStatus1;
|
||||
this.printMessageIO(port, null, addrFrom, "STATUS1", b, true);
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* inSIStatus2(port, addrFrom)
|
||||
*
|
||||
* @this {ChipSet}
|
||||
* @param {number} port (0x02)
|
||||
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
|
||||
* @return {number} simulated port value
|
||||
*/
|
||||
ChipSet.prototype.inSIStatus2 = function(port, addrFrom)
|
||||
{
|
||||
var b = this.bStatus2;
|
||||
this.printMessageIO(port, null, addrFrom, "STATUS2", b, true);
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* inSIShiftResult(port, addrFrom)
|
||||
*
|
||||
* @this {ChipSet}
|
||||
* @param {number} port (0x03)
|
||||
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
|
||||
* @return {number} simulated port value
|
||||
*/
|
||||
ChipSet.prototype.inSIShiftResult = function(port, addrFrom)
|
||||
{
|
||||
var b = ((this.bShiftData << this.bShiftCount) >> 8) & 0xff;
|
||||
this.printMessageIO(port, null, addrFrom, "SHIFT.RESULT", b, true);
|
||||
return b;
|
||||
};
|
||||
|
||||
/**
|
||||
* outSIShiftCount(port, b, addrFrom)
|
||||
*
|
||||
* @this {ChipSet}
|
||||
* @param {number} port (0x02)
|
||||
* @param {number} b
|
||||
* @param {number} [addrFrom] (not defined if the Debugger is trying to write the specified port)
|
||||
*/
|
||||
ChipSet.prototype.outSIShiftCount = function(port, b, addrFrom)
|
||||
{
|
||||
this.printMessageIO(port, b, addrFrom, "SHIFT.COUNT", null, true);
|
||||
this.bShiftCount = b;
|
||||
};
|
||||
|
||||
/**
|
||||
* outSISound1(port, b, addrFrom)
|
||||
*
|
||||
* @this {ChipSet}
|
||||
* @param {number} port (0x03)
|
||||
* @param {number} b
|
||||
* @param {number} [addrFrom] (not defined if the Debugger is trying to write the specified port)
|
||||
*/
|
||||
ChipSet.prototype.outSISound1 = function(port, b, addrFrom)
|
||||
{
|
||||
this.printMessageIO(port, b, addrFrom, "SOUND1", null, true);
|
||||
this.bSound1 = b;
|
||||
};
|
||||
|
||||
/**
|
||||
* outSIShiftData(port, b, addrFrom)
|
||||
*
|
||||
* @this {ChipSet}
|
||||
* @param {number} port (0x04)
|
||||
* @param {number} b
|
||||
* @param {number} [addrFrom] (not defined if the Debugger is trying to write the specified port)
|
||||
*/
|
||||
ChipSet.prototype.outSIShiftData = function(port, b, addrFrom)
|
||||
{
|
||||
this.printMessageIO(port, b, addrFrom, "SHIFT.DATA", null, true);
|
||||
this.bShiftData = b;
|
||||
};
|
||||
|
||||
/**
|
||||
* outSISound2(port, b, addrFrom)
|
||||
*
|
||||
* @this {ChipSet}
|
||||
* @param {number} port (0x05)
|
||||
* @param {number} b
|
||||
* @param {number} [addrFrom] (not defined if the Debugger is trying to write the specified port)
|
||||
*/
|
||||
ChipSet.prototype.outSISound2 = function(port, b, addrFrom)
|
||||
{
|
||||
this.printMessageIO(port, b, addrFrom, "SOUND2", null, true);
|
||||
this.bSound2 = b;
|
||||
};
|
||||
|
||||
/*
|
||||
* Port input notification tables
|
||||
*/
|
||||
ChipSet.aPortInput = {
|
||||
0x00: ChipSet.prototype.inSIStatus0,
|
||||
0x01: ChipSet.prototype.inSIStatus1,
|
||||
0x02: ChipSet.prototype.inSIStatus2,
|
||||
0x03: ChipSet.prototype.inSIShiftResult
|
||||
};
|
||||
|
||||
/*
|
||||
* Port output notification tables
|
||||
*/
|
||||
ChipSet.aPortOutput = {
|
||||
0x02: ChipSet.prototype.outSIShiftCount,
|
||||
0x03: ChipSet.prototype.outSISound1,
|
||||
0x04: ChipSet.prototype.outSIShiftData,
|
||||
0x05: ChipSet.prototype.outSISound2
|
||||
};
|
||||
|
||||
/**
|
||||
* ChipSet.init()
|
||||
*
|
||||
* This function operates on every HTML element of class "chipset", extracting the
|
||||
* JSON-encoded parameters for the ChipSet constructor from the element's "data-value"
|
||||
* attribute, invoking the constructor to create a ChipSet component, and then binding
|
||||
* any associated HTML controls to the new component.
|
||||
*/
|
||||
ChipSet.init = function()
|
||||
{
|
||||
var aeChipSet = Component.getElementsByClass(document, PCJSCLASS, "chipset");
|
||||
for (var iChip = 0; iChip < aeChipSet.length; iChip++) {
|
||||
var eChipSet = aeChipSet[iChip];
|
||||
var parmsChipSet = Component.getComponentParms(eChipSet);
|
||||
var chipset = new ChipSet(parmsChipSet);
|
||||
Component.bindComponentControls(chipset, eChipSet, PCJSCLASS);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize every ChipSet module on the page.
|
||||
*/
|
||||
web.onInit(ChipSet.init);
|
||||
|
||||
if (NODE) module.exports = ChipSet;
|
||||
|
|
@ -1479,22 +1479,18 @@ Computer.prototype.updateStatus = function(fForce)
|
|||
};
|
||||
|
||||
/**
|
||||
* updateVideo(fForce)
|
||||
* updateVideo(n)
|
||||
*
|
||||
* Any high-frequency updates should be performed here. Avoid DOM updates, since updateVideo() can be called up to
|
||||
* 60 times per second (see VIDEO_UPDATES_PER_SECOND).
|
||||
* Any high-frequency updates should be performed here (avoid updating DOM elements).
|
||||
*
|
||||
* @this {Computer}
|
||||
* @param {boolean} [fForce] (true to force a video update)
|
||||
* @param {number} n (where 0 <= n < VIDEO_UPDATES_PER_SECOND for a normal update, or -1 for a forced update)
|
||||
*/
|
||||
Computer.prototype.updateVideo = function(fForce)
|
||||
Computer.prototype.updateVideo = function(n)
|
||||
{
|
||||
/*
|
||||
for (var i = 0; i < this.aVideo.length; i++) {
|
||||
this.aVideo[i].updateScreen(fForce);
|
||||
this.aVideo[i].updateScreen(n);
|
||||
}
|
||||
if (this.panel) this.panel.updateAnimation();
|
||||
*/
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ function CPU(parmsCPU, nCyclesDefault)
|
|||
|
||||
this.aCounts = {};
|
||||
this.aCounts.nCyclesPerSecond = nCycles;
|
||||
this.aCounts.nVideoUpdates = 0;
|
||||
|
||||
/*
|
||||
* nCyclesMultiplier replaces the old "speed" variable (0, 1, 2) and eliminates the need for
|
||||
|
|
@ -148,7 +149,7 @@ Component.subclass(CPU);
|
|||
* this.aCounts.nCyclesNextStatusUpdate <= this.aCounts.nCyclesPerStatusUpdate
|
||||
*/
|
||||
CPU.YIELDS_PER_SECOND = 30;
|
||||
CPU.VIDEO_UPDATES_PER_SECOND = 60; // WARNING: if you change this, beware of side-effects in the Video component
|
||||
CPU.VIDEO_UPDATES_PER_SECOND = 60;
|
||||
CPU.STATUS_UPDATES_PER_SECOND = 2;
|
||||
|
||||
CPU.BUTTONS = ["power", "reset"];
|
||||
|
|
@ -173,13 +174,14 @@ CPU.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
if (control) this.cmp.setBinding(null, CPU.BUTTONS[i], control);
|
||||
}
|
||||
|
||||
this.fpu = cmp.getMachineComponent("FPU");
|
||||
/*
|
||||
* We need to know the refresh rate (and corresponding interrupt rate, if any) of the Video component.
|
||||
*/
|
||||
var video = cmp.getMachineComponent("Video");
|
||||
this.refreshRate = video && video.getRefreshRate() || CPU.VIDEO_UPDATES_PER_SECOND;
|
||||
|
||||
/*
|
||||
* Attach the ChipSet component to the CPU so that it can obtain the IDT vector number
|
||||
* of pending hardware interrupts in response to the ChipSet's updateINTR() notifications.
|
||||
*
|
||||
* We must also call chipset.updateAllTimers() periodically; stepCPU() takes care of that.
|
||||
* Attach the ChipSet component to the CPU so that it can be notified whenever the CPU stops and starts.
|
||||
*/
|
||||
this.chipset = cmp.getMachineComponent("ChipSet");
|
||||
|
||||
|
|
@ -197,12 +199,11 @@ CPU.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
/**
|
||||
* reset()
|
||||
*
|
||||
* This is a placeholder for reset (overridden by the CPUSim component).
|
||||
*
|
||||
* @this {CPU}
|
||||
*/
|
||||
CPU.prototype.reset = function()
|
||||
{
|
||||
this.aCounts.nVideoUpdates = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -550,7 +551,7 @@ CPU.prototype.setBurstCycles = function(nCycles)
|
|||
* costs are being properly assessed, then we need to update nSnapCycles as well.
|
||||
*
|
||||
* TODO: If the delta is negative, we could simply ignore the request, but we must first carefully
|
||||
* consider the impact on the ChipSet timers.
|
||||
* consider the impact on the ChipSet timers, if any.
|
||||
*/
|
||||
// if (DEBUG) this.nSnapCycles -= nDelta;
|
||||
this.nStepCycles -= nDelta;
|
||||
|
|
@ -606,7 +607,7 @@ CPU.prototype.calcCycles = function(fRecalc)
|
|||
* Calculate the most cycles we're allowed to execute in a single "burst"
|
||||
*/
|
||||
var nMostUpdatesPerSecond = CPU.YIELDS_PER_SECOND;
|
||||
if (nMostUpdatesPerSecond < CPU.VIDEO_UPDATES_PER_SECOND) nMostUpdatesPerSecond = CPU.VIDEO_UPDATES_PER_SECOND;
|
||||
if (nMostUpdatesPerSecond < this.refreshRate) nMostUpdatesPerSecond = this.refreshRate;
|
||||
if (nMostUpdatesPerSecond < CPU.STATUS_UPDATES_PER_SECOND) nMostUpdatesPerSecond = CPU.STATUS_UPDATES_PER_SECOND;
|
||||
|
||||
/*
|
||||
|
|
@ -622,7 +623,7 @@ CPU.prototype.calcCycles = function(fRecalc)
|
|||
this.aCounts.msPerYield = Math.round(1000 / CPU.YIELDS_PER_SECOND);
|
||||
this.aCounts.nCyclesPerBurst = Math.floor(this.aCounts.nCyclesPerSecond / nMostUpdatesPerSecond * vMultiplier);
|
||||
this.aCounts.nCyclesPerYield = Math.floor(this.aCounts.nCyclesPerSecond / CPU.YIELDS_PER_SECOND * vMultiplier);
|
||||
this.aCounts.nCyclesPerVideoUpdate = Math.floor(this.aCounts.nCyclesPerSecond / CPU.VIDEO_UPDATES_PER_SECOND * vMultiplier);
|
||||
this.aCounts.nCyclesPerVideoUpdate = Math.floor(this.aCounts.nCyclesPerSecond / this.refreshRate * vMultiplier);
|
||||
this.aCounts.nCyclesPerStatusUpdate = Math.floor(this.aCounts.nCyclesPerSecond / CPU.STATUS_UPDATES_PER_SECOND * vMultiplier);
|
||||
|
||||
/*
|
||||
|
|
@ -807,7 +808,6 @@ CPU.prototype.calcSpeed = function(nCycles, msElapsed)
|
|||
this.aCounts.mhz = Math.round(nCycles / (msElapsed * 10)) / 100;
|
||||
if (msElapsed >= 86400000) {
|
||||
this.nTotalCycles = 0;
|
||||
if (this.chipset) this.chipset.updateAllTimers(true);
|
||||
this.setSpeed(); // reset all counters once per day so that we never have to worry about overflow
|
||||
}
|
||||
}
|
||||
|
|
@ -965,12 +965,6 @@ CPU.prototype.runCPU = function(fUpdateFocus)
|
|||
do {
|
||||
var nCyclesPerBurst = (this.flags.fChecksum? 1 : this.aCounts.nCyclesPerBurst);
|
||||
|
||||
if (this.chipset) {
|
||||
this.chipset.updateAllTimers();
|
||||
nCyclesPerBurst = this.chipset.getTimerCycleLimit(0, nCyclesPerBurst);
|
||||
nCyclesPerBurst = this.chipset.getRTCCycleLimit(nCyclesPerBurst);
|
||||
}
|
||||
|
||||
/*
|
||||
* nCyclesPerBurst is how many cycles we WANT to run on each iteration of stepCPU(), but it may run
|
||||
* significantly less (or slightly more, since we can't execute partial instructions).
|
||||
|
|
@ -1005,7 +999,8 @@ CPU.prototype.runCPU = function(fUpdateFocus)
|
|||
this.aCounts.nCyclesNextVideoUpdate -= nCycles;
|
||||
if (this.aCounts.nCyclesNextVideoUpdate <= 0) {
|
||||
this.aCounts.nCyclesNextVideoUpdate += this.aCounts.nCyclesPerVideoUpdate;
|
||||
if (this.cmp) this.cmp.updateVideo();
|
||||
if (this.cmp) this.cmp.updateVideo(this.aCounts.nVideoUpdates++);
|
||||
if (this.aCounts.nVideoUpdates > this.refreshRate) this.aCounts.nVideoUpdates = 0;
|
||||
}
|
||||
|
||||
this.aCounts.nCyclesNextStatusUpdate -= nCycles;
|
||||
|
|
@ -1117,7 +1112,7 @@ CPU.prototype.stopCPU = function(fComplete)
|
|||
CPU.prototype.updateCPU = function(fForce)
|
||||
{
|
||||
if (this.cmp) {
|
||||
this.cmp.updateVideo(fForce);
|
||||
this.cmp.updateVideo(-1);
|
||||
this.cmp.updateStatus(fForce);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -91,28 +91,22 @@ var CPUDef = {
|
|||
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
|
||||
INTL: 0x07, // last interrupt level requested
|
||||
INTR: 0x08, // set if interrupt has been requested
|
||||
HALT: 0x10 // halt requested; see the HLT opcode
|
||||
},
|
||||
/*
|
||||
* Opcode definitions
|
||||
*/
|
||||
OPCODE: {
|
||||
ACI: 0xCE, // PS.ALL
|
||||
CALL: 0xCD
|
||||
CALL: 0xCD,
|
||||
RST0: 0xC7
|
||||
// to be continued....
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2747,7 +2747,7 @@ CPUDef.opCP = function()
|
|||
*/
|
||||
CPUDef.opPUPSW = function()
|
||||
{
|
||||
this.pushWord(this.getPS() | (this.regA << 8));
|
||||
this.pushWord((this.getPS() & 0xff) | (this.regA << 8));
|
||||
this.nStepCycles -= 11;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -226,6 +226,7 @@ CPUSim.prototype.reset = function()
|
|||
this.resetRegs();
|
||||
this.resetCycles();
|
||||
this.clearError(); // clear any fatal error/exception that setError() may have flagged
|
||||
this.parent.reset.call(this);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -255,8 +256,6 @@ CPUSim.prototype.resetRegs = function()
|
|||
* intFlags contains some internal states we use to indicate whether a hardware interrupt (INTFLAG.INTR) or
|
||||
* Trap software interrupt (INTR.TRAP) has been requested, as well as when we're in a "HLT" state (INTFLAG.HALT)
|
||||
* that requires us to wait for a hardware interrupt (INTFLAG.INTR) before continuing execution.
|
||||
*
|
||||
* intFlags must be cleared only by checkINTR(), whereas opFlags must be cleared prior to every CPU operation.
|
||||
*/
|
||||
this.intFlags = CPUDef.INTFLAG.NONE;
|
||||
};
|
||||
|
|
@ -286,7 +285,7 @@ CPUSim.prototype.save = function()
|
|||
{
|
||||
var state = new State(this);
|
||||
state.set(0, [this.regA, this.regB, this.regC, this.regD, this.regE, this.regH, this.regL, this.getSP(), this.getPC(), this.getPS()]);
|
||||
state.set(1, [this.opFlags, this.intFlags, this.nTotalCycles, this.getSpeed()]);
|
||||
state.set(1, [this.intFlags, this.nTotalCycles, this.getSpeed()]);
|
||||
state.set(2, this.bus.saveMemory());
|
||||
return state.data();
|
||||
};
|
||||
|
|
@ -314,9 +313,8 @@ CPUSim.prototype.restore = function(data)
|
|||
this.setPC(a[8]);
|
||||
this.setPS(a[9]);
|
||||
a = data[1];
|
||||
this.opFlags = a[0];
|
||||
this.intFlags = a[1];
|
||||
this.nTotalCycles = a[2];
|
||||
this.intFlags = a[0];
|
||||
this.nTotalCycles = a[1];
|
||||
this.setSpeed(a[3]);
|
||||
return this.bus.restoreMemory(data[2]);
|
||||
};
|
||||
|
|
@ -965,39 +963,52 @@ CPUSim.prototype.pushWord = function(w)
|
|||
/**
|
||||
* checkINTR()
|
||||
*
|
||||
* This must only be called when intFlags (containing the simulated INTFLAG.INTR signal) is known to be set.
|
||||
* Note that it's perfectly possible that between the time updateINTR(true) was called and we request the
|
||||
* interrupt vector number below, the interrupt could have been cleared or masked, in which case getIRRVector()
|
||||
* will return -1 and we'll simply clear INTFLAG.INTR.
|
||||
*
|
||||
* @this {CPUSim}
|
||||
* @return {boolean} true if h/w interrupt (or trap) has just been acknowledged, false if not
|
||||
* @return {boolean} true if h/w interrupt has just been acknowledged, false if not
|
||||
*/
|
||||
CPUSim.prototype.checkINTR = function()
|
||||
{
|
||||
if ((this.intFlags & CPUDef.INTFLAG.INTR) && this.getIF()) {
|
||||
var bRST = CPUDef.OPCODE.RST0 | ((this.intFlags & CPUDef.INTFLAG.INTL) << 3);
|
||||
this.clearINTR();
|
||||
this.clearIF();
|
||||
this.aOps[bRST].call(this);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* updateINTR(fRaise)
|
||||
*
|
||||
* This is called by the ChipSet component whenever a h/w interrupt needs to be simulated.
|
||||
* This is how the PIC component simulates raising the INTFLAG.INTR signal. We will honor the request
|
||||
* only if we have a reference back to the ChipSet component. The CPU will then "respond" by calling
|
||||
* checkINTR() and request the corresponding interrupt vector from the ChipSet.
|
||||
* clearINTR()
|
||||
*
|
||||
* @this {CPUSim}
|
||||
* @param {boolean} fRaise is true to raise INTFLAG.INTR, false to lower
|
||||
*/
|
||||
CPUSim.prototype.updateINTR = function(fRaise)
|
||||
CPUSim.prototype.clearINTR = function()
|
||||
{
|
||||
if (this.chipset) {
|
||||
if (fRaise) {
|
||||
this.intFlags |= CPUDef.INTFLAG.INTR;
|
||||
} else {
|
||||
this.intFlags &= ~CPUDef.INTFLAG.INTR;
|
||||
}
|
||||
}
|
||||
this.intFlags &= ~(CPUDef.INTFLAG.INTL | CPUDef.INTFLAG.INTR);
|
||||
};
|
||||
|
||||
/**
|
||||
* requestINTR(nLevel)
|
||||
*
|
||||
* This is called by any component that wants to request a h/w interrupt.
|
||||
*
|
||||
* NOTE: We allow INTR to be set regardless of the current state of interrupt flag (IF), on the theory
|
||||
* that if/when the CPU briefly turns interrupts off, it shouldn't lose the last h/w interrupt requested.
|
||||
* So instead of ignoring INTR here, checkINTR() ignores INTR as long as the interrupt flag (IF) is clear.
|
||||
*
|
||||
* The downside is that, as long as the CPU has interrupts disabled, an active INTR state will slow stepCPU()
|
||||
* down slightly. We could avoid that by introducing a two-stage interrupt tracking system, where a separate
|
||||
* variable keeps track of the last interrupt requested whenever the interrupt flag (IF) is clear, and when
|
||||
* setIF() finally occurs, that interrupt is propagated to intFlags. But for now, we're going to assume that
|
||||
* scenario is rare.
|
||||
*
|
||||
* @this {CPUSim}
|
||||
* @param {number} nLevel (0-7)
|
||||
*/
|
||||
CPUSim.prototype.requestINTR = function(nLevel)
|
||||
{
|
||||
this.intFlags = (this.intFlags & ~CPUDef.INTFLAG.INTL) | nLevel | CPUDef.INTFLAG.INTR;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1110,27 +1121,6 @@ CPUSim.prototype.stepCPU = function(nMinCycles)
|
|||
*/
|
||||
this.nBurstCycles = this.nStepCycles = nMinCycles;
|
||||
|
||||
/*
|
||||
* NOTE: Even though runCPU() calls updateAllTimers(), we need an additional call here if we're being
|
||||
* called from the Debugger, so that any single-stepping will update the timers as well.
|
||||
*/
|
||||
if (this.chipset && !nMinCycles) this.chipset.updateAllTimers();
|
||||
|
||||
/*
|
||||
* Let's also suppress h/w interrupts whenever the Debugger is single-stepping an instruction; I'm loathe
|
||||
* to allow Debugger interactions to affect the behavior of the virtual machine in ANY way, but I'm making
|
||||
* this small concession to avoid the occasional and sometimes unexpected Debugger command that ends up
|
||||
* stepping into a hardware interrupt service routine (ISR).
|
||||
*
|
||||
* Note that this is similar to the problem discussed in checkINTR() regarding the priority of external h/w
|
||||
* interrupts vs. Trap interrupts, but they require different solutions, because our Debugger operates
|
||||
* independently of the CPU.
|
||||
*
|
||||
* One exception I make here is when you've asked the Debugger to display PIC messages, the idea being that
|
||||
* if you're watching the PIC that closely, then you want to hardware interrupts to occur regardless.
|
||||
*/
|
||||
if (!nMinCycles) this.opFlags |= CPUDef.OPFLAG.NOINTR;
|
||||
|
||||
do {
|
||||
if (this.intFlags) {
|
||||
if (this.checkINTR()) {
|
||||
|
|
@ -1138,12 +1128,11 @@ CPUSim.prototype.stepCPU = function(nMinCycles)
|
|||
this.assert(DEBUGGER); // nMinCycles of zero should be generated ONLY by the Debugger
|
||||
if (DEBUGGER) {
|
||||
this.println("interrupt dispatched");
|
||||
this.opFlags = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.intFlags & CPUDef.INTFLAG.HALT) {
|
||||
else if (this.intFlags & CPUDef.INTFLAG.HALT) {
|
||||
/*
|
||||
* As discussed in opHLT(), the CPU is never REALLY halted by a HLT instruction; instead,
|
||||
* opHLT() sets CPUDef.INTFLAG.HALT, signalling to us that we're free to end the current burst
|
||||
|
|
@ -1160,7 +1149,6 @@ CPUSim.prototype.stepCPU = function(nMinCycles)
|
|||
* number of cycles during which we did not actually execute any instructions).
|
||||
*/
|
||||
this.nStepCycles = 0;
|
||||
this.opFlags = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -1173,8 +1161,6 @@ CPUSim.prototype.stepCPU = function(nMinCycles)
|
|||
nDebugState = 1;
|
||||
}
|
||||
|
||||
this.opFlags = 0;
|
||||
|
||||
this.aOps[this.getPCByte()].call(this);
|
||||
|
||||
} while (this.nStepCycles > 0);
|
||||
|
|
|
|||
|
|
@ -359,7 +359,7 @@ if (DEBUGGER) {
|
|||
Debugger.TYPE_E = (Debugger.REG_E << 8 | Debugger.TYPE_REG | Debugger.TYPE_BYTE);
|
||||
Debugger.TYPE_H = (Debugger.REG_H << 8 | Debugger.TYPE_REG | Debugger.TYPE_BYTE);
|
||||
Debugger.TYPE_L = (Debugger.REG_L << 8 | Debugger.TYPE_REG | Debugger.TYPE_BYTE);
|
||||
Debugger.TYPE_M = (Debugger.REG_M << 8 | Debugger.TYPE_REG | Debugger.TYPE_BYTE);
|
||||
Debugger.TYPE_M = (Debugger.REG_M << 8 | Debugger.TYPE_REG | Debugger.TYPE_BYTE | Debugger.TYPE_MEM);
|
||||
Debugger.TYPE_BC = (Debugger.REG_BC << 8 | Debugger.TYPE_REG | Debugger.TYPE_WORD);
|
||||
Debugger.TYPE_DE = (Debugger.REG_DE << 8 | Debugger.TYPE_REG | Debugger.TYPE_WORD);
|
||||
Debugger.TYPE_HL = (Debugger.REG_HL << 8 | Debugger.TYPE_REG | Debugger.TYPE_WORD);
|
||||
|
|
@ -404,7 +404,7 @@ if (DEBUGGER) {
|
|||
/* 0x06 */ [Debugger.INS.MVI, Debugger.TYPE_B, Debugger.TYPE_IMM],
|
||||
/* 0x07 */ [Debugger.INS.RLC],
|
||||
/* 0x08 */ [Debugger.INS.NOP, Debugger.TYPE_UNDOC],
|
||||
/* 0x09 */ [Debugger.INS.DAD, Debugger.TYPE_BC | Debugger.TYPE_IN], // REG_HL is the implied (unlisted) destination
|
||||
/* 0x09 */ [Debugger.INS.DAD, Debugger.TYPE_HL, Debugger.TYPE_BC | Debugger.TYPE_IN], // let's be more explicit about the operands
|
||||
/* 0x0A */ [Debugger.INS.LDAX, Debugger.TYPE_BC | Debugger.TYPE_MEM],
|
||||
/* 0x0B */ [Debugger.INS.DCX, Debugger.TYPE_BC],
|
||||
/* 0x0C */ [Debugger.INS.INR, Debugger.TYPE_C],
|
||||
|
|
@ -420,7 +420,7 @@ if (DEBUGGER) {
|
|||
/* 0x16 */ [Debugger.INS.MVI, Debugger.TYPE_D, Debugger.TYPE_IMM],
|
||||
/* 0x17 */ [Debugger.INS.RAL],
|
||||
/* 0x18 */ [Debugger.INS.NOP, Debugger.TYPE_UNDOC],
|
||||
/* 0x19 */ [Debugger.INS.DAD, Debugger.TYPE_DE | Debugger.TYPE_IN], // REG_HL is the implied (unlisted) destination
|
||||
/* 0x19 */ [Debugger.INS.DAD, Debugger.TYPE_HL, Debugger.TYPE_DE | Debugger.TYPE_IN], // let's be more explicit about the operands
|
||||
/* 0x1A */ [Debugger.INS.LDAX, Debugger.TYPE_DE | Debugger.TYPE_MEM],
|
||||
/* 0x1B */ [Debugger.INS.DCX, Debugger.TYPE_DE],
|
||||
/* 0x1C */ [Debugger.INS.INR, Debugger.TYPE_E],
|
||||
|
|
@ -436,7 +436,7 @@ if (DEBUGGER) {
|
|||
/* 0x26 */ [Debugger.INS.MVI, Debugger.TYPE_H, Debugger.TYPE_IMM],
|
||||
/* 0x27 */ [Debugger.INS.DAA],
|
||||
/* 0x28 */ [Debugger.INS.NOP, Debugger.TYPE_UNDOC],
|
||||
/* 0x29 */ [Debugger.INS.DAD, Debugger.TYPE_HL | Debugger.TYPE_IN], // REG_HL is the implied (unlisted) destination
|
||||
/* 0x29 */ [Debugger.INS.DAD, Debugger.TYPE_HL, Debugger.TYPE_HL | Debugger.TYPE_IN], // let's be more explicit about the operands
|
||||
/* 0x2A */ [Debugger.INS.LHLD, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_MEM],
|
||||
/* 0x2B */ [Debugger.INS.DCX, Debugger.TYPE_HL],
|
||||
/* 0x2C */ [Debugger.INS.INR, Debugger.TYPE_L],
|
||||
|
|
@ -445,15 +445,15 @@ if (DEBUGGER) {
|
|||
/* 0x2F */ [Debugger.INS.CMA],
|
||||
/* 0x30 */ [Debugger.INS.NOP, Debugger.TYPE_UNDOC],
|
||||
/* 0x31 */ [Debugger.INS.LXI, Debugger.TYPE_SP, Debugger.TYPE_IMM],
|
||||
/* 0x32 */ [Debugger.INS.STA, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_MEM],
|
||||
/* 0x32 */ [Debugger.INS.STA, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_MEM],
|
||||
/* 0x33 */ [Debugger.INS.INX, Debugger.TYPE_SP],
|
||||
/* 0x34 */ [Debugger.INS.INR, Debugger.TYPE_M],
|
||||
/* 0x35 */ [Debugger.INS.DCR, Debugger.TYPE_M],
|
||||
/* 0x36 */ [Debugger.INS.MVI, Debugger.TYPE_M, Debugger.TYPE_IMM],
|
||||
/* 0x37 */ [Debugger.INS.STC],
|
||||
/* 0x38 */ [Debugger.INS.NOP, Debugger.TYPE_UNDOC],
|
||||
/* 0x39 */ [Debugger.INS.DAD, Debugger.TYPE_SP | Debugger.TYPE_IN], // REG_HL is the implied (unlisted) destination
|
||||
/* 0x3A */ [Debugger.INS.LDA, Debugger.TYPE_IMM | Debugger.TYPE_BYTE | Debugger.TYPE_MEM],
|
||||
/* 0x39 */ [Debugger.INS.DAD, Debugger.TYPE_HL, Debugger.TYPE_SP | Debugger.TYPE_IN], // let's be more explicit about the operands
|
||||
/* 0x3A */ [Debugger.INS.LDA, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_MEM],
|
||||
/* 0x3B */ [Debugger.INS.DCX, Debugger.TYPE_SP],
|
||||
/* 0x3C */ [Debugger.INS.INR, Debugger.TYPE_A],
|
||||
/* 0x3D */ [Debugger.INS.DCR, Debugger.TYPE_A],
|
||||
|
|
@ -622,15 +622,15 @@ if (DEBUGGER) {
|
|||
/* 0xE0 */ [Debugger.INS.RPO],
|
||||
/* 0xE1 */ [Debugger.INS.POP, Debugger.TYPE_HL],
|
||||
/* 0xE2 */ [Debugger.INS.JPO, Debugger.TYPE_IMM | Debugger.TYPE_WORD],
|
||||
/* 0xE3 */ [Debugger.INS.XTHL],
|
||||
/* 0xE3 */ [Debugger.INS.XTHL, Debugger.TYPE_SP | Debugger.TYPE_MEM, Debugger.TYPE_HL], // let's be more explicit about the operands
|
||||
/* 0xE4 */ [Debugger.INS.CPO, Debugger.TYPE_IMM | Debugger.TYPE_WORD],
|
||||
/* 0xE5 */ [Debugger.INS.PUSH, Debugger.TYPE_HL],
|
||||
/* 0xE6 */ [Debugger.INS.ANI, Debugger.TYPE_IMM | Debugger.TYPE_BYTE],
|
||||
/* 0xE7 */ [Debugger.INS.RST],
|
||||
/* 0xE8 */ [Debugger.INS.RPE],
|
||||
/* 0xE9 */ [Debugger.INS.PCHL],
|
||||
/* 0xE9 */ [Debugger.INS.PCHL, Debugger.TYPE_PC, Debugger.TYPE_HL], // let's be more explicit about the operands
|
||||
/* 0xEA */ [Debugger.INS.JPE, Debugger.TYPE_IMM | Debugger.TYPE_WORD],
|
||||
/* 0xEB */ [Debugger.INS.XCHG],
|
||||
/* 0xEB */ [Debugger.INS.XCHG, Debugger.TYPE_HL, Debugger.TYPE_DE], // let's be more explicit about the operands
|
||||
/* 0xEC */ [Debugger.INS.CPE, Debugger.TYPE_IMM | Debugger.TYPE_WORD],
|
||||
/* 0xED */ [Debugger.INS.CALL, Debugger.TYPE_IMM | Debugger.TYPE_WORD | Debugger.TYPE_UNDOC],
|
||||
/* 0xEE */ [Debugger.INS.XRI, Debugger.TYPE_IMM | Debugger.TYPE_BYTE],
|
||||
|
|
@ -644,7 +644,7 @@ if (DEBUGGER) {
|
|||
/* 0xF6 */ [Debugger.INS.ORI, Debugger.TYPE_IMM | Debugger.TYPE_BYTE],
|
||||
/* 0xF7 */ [Debugger.INS.RST],
|
||||
/* 0xF8 */ [Debugger.INS.RM],
|
||||
/* 0xF9 */ [Debugger.INS.SPHL],
|
||||
/* 0xF9 */ [Debugger.INS.SPHL, Debugger.TYPE_SP, Debugger.TYPE_HL], // let's be more explicit about the operands
|
||||
/* 0xFA */ [Debugger.INS.JM, Debugger.TYPE_IMM | Debugger.TYPE_WORD],
|
||||
/* 0xFB */ [Debugger.INS.EI],
|
||||
/* 0xFC */ [Debugger.INS.CM, Debugger.TYPE_IMM | Debugger.TYPE_WORD],
|
||||
|
|
@ -672,7 +672,7 @@ if (DEBUGGER) {
|
|||
"bus": Messages.BUS,
|
||||
"mem": Messages.MEM,
|
||||
"port": Messages.PORT,
|
||||
"timer": Messages.TIMER,
|
||||
"chipset": Messages.CHIPSET,
|
||||
"keyboard": Messages.KEYBOARD, // "kbd" is also allowed as shorthand for "keyboard"; see doMessages()
|
||||
"key": Messages.KEYS, // using "key" instead of "keys", since the latter is a method on JavasScript objects
|
||||
"video": Messages.VIDEO,
|
||||
|
|
@ -1384,6 +1384,7 @@ if (DEBUGGER) {
|
|||
case Debugger.REG_E:
|
||||
case Debugger.REG_H:
|
||||
case Debugger.REG_L:
|
||||
case Debugger.REG_M:
|
||||
case Debugger.REG_F:
|
||||
cch = 2;
|
||||
break;
|
||||
|
|
@ -1417,7 +1418,7 @@ if (DEBUGGER) {
|
|||
n = cpu.regA;
|
||||
break;
|
||||
case Debugger.REG_F:
|
||||
n = cpu.getPS() & 0xff;
|
||||
n = cpu.getPS();
|
||||
break;
|
||||
case Debugger.REG_B:
|
||||
n = cpu.regB;
|
||||
|
|
@ -1426,7 +1427,7 @@ if (DEBUGGER) {
|
|||
n = cpu.regC;
|
||||
break;
|
||||
case Debugger.REG_BC:
|
||||
n = (cpu.regB << 8) | cpu.regC;
|
||||
n = cpu.getBC();
|
||||
break;
|
||||
case Debugger.REG_D:
|
||||
n = cpu.regD;
|
||||
|
|
@ -1435,7 +1436,7 @@ if (DEBUGGER) {
|
|||
n = cpu.regE;
|
||||
break;
|
||||
case Debugger.REG_DE:
|
||||
n = (cpu.regD << 8) | cpu.regE;
|
||||
n = cpu.getDE();
|
||||
break;
|
||||
case Debugger.REG_H:
|
||||
n = cpu.regH;
|
||||
|
|
@ -1444,7 +1445,10 @@ if (DEBUGGER) {
|
|||
n = cpu.regL;
|
||||
break;
|
||||
case Debugger.REG_HL:
|
||||
n = (cpu.regH << 8) | cpu.regL;
|
||||
n = cpu.getHL();
|
||||
break;
|
||||
case Debugger.REG_M:
|
||||
n = cpu.getByte(cpu.getHL());
|
||||
break;
|
||||
case Debugger.REG_PS:
|
||||
n = (cpu.regA << 8) | (cpu.getPS() & 0xff);
|
||||
|
|
@ -2430,31 +2434,38 @@ if (DEBUGGER) {
|
|||
var iIns = aOpDesc[0];
|
||||
|
||||
var sOperands = "";
|
||||
var typeSizeDefault = null;
|
||||
var sOpcode = asOpcodes[iIns];
|
||||
var cOperands = aOpDesc.length - 1;
|
||||
var typeSizeDefault = Debugger.TYPE_NONE, type;
|
||||
|
||||
for (var iOperand = 1; iOperand <= cOperands; iOperand++) {
|
||||
|
||||
var disp, off, cch;
|
||||
var sOperand = "";
|
||||
var type = aOpDesc[iOperand];
|
||||
|
||||
type = aOpDesc[iOperand];
|
||||
if (type === undefined) continue;
|
||||
|
||||
var typeSize = type & Debugger.TYPE_SIZE;
|
||||
if (typeSize == Debugger.TYPE_NONE) {
|
||||
if (typeSizeDefault == null) continue;
|
||||
typeSize = typeSizeDefault;
|
||||
if (!typeSize) {
|
||||
type |= typeSizeDefault;
|
||||
} else {
|
||||
typeSizeDefault = typeSize;
|
||||
}
|
||||
|
||||
var typeOther = type & Debugger.TYPE_OTHER;
|
||||
if (!typeOther) {
|
||||
type |= (iOperand == 1? Debugger.TYPE_OUT : Debugger.TYPE_IN);
|
||||
}
|
||||
typeSizeDefault = typeSize;
|
||||
|
||||
var typeMode = type & Debugger.TYPE_MODE;
|
||||
if (typeMode == Debugger.TYPE_IMM) {
|
||||
if (typeMode & Debugger.TYPE_IMM) {
|
||||
sOperand = this.getImmOperand(type, dbgAddr);
|
||||
}
|
||||
else if (typeMode == Debugger.TYPE_REG) {
|
||||
else if (typeMode & Debugger.TYPE_REG) {
|
||||
sOperand = this.getRegOperand((type & Debugger.TYPE_IREG) >> 8, type, dbgAddr);
|
||||
}
|
||||
|
||||
if (!sOperand || !sOperand.length) {
|
||||
sOperands = "INVALID";
|
||||
break;
|
||||
|
|
@ -2472,7 +2483,8 @@ if (DEBUGGER) {
|
|||
} while (dbgAddrIns.addr != dbgAddr.addr);
|
||||
}
|
||||
|
||||
sLine += str.pad(sBytes, 11);
|
||||
sLine += str.pad(sBytes, 10);
|
||||
sLine += (type & Debugger.TYPE_UNDOC)? '*' : ' ';
|
||||
sLine += str.pad(sOpcode, 7);
|
||||
if (sOperands) sLine += ' ' + sOperands;
|
||||
|
||||
|
|
@ -2503,14 +2515,7 @@ if (DEBUGGER) {
|
|||
|
||||
switch (typeSize) {
|
||||
case Debugger.TYPE_BYTE:
|
||||
/*
|
||||
* There's the occasional immediate byte we don't need to display (eg, the 0x0A
|
||||
* following an AAM or AAD instruction), so we suppress the byte if it lacks a TYPE_IN
|
||||
* or TYPE_OUT designation (and TYPE_BOTH, as the name implies, includes both).
|
||||
*/
|
||||
if (type & Debugger.TYPE_BOTH) {
|
||||
sOperand = str.toHex(this.getByte(dbgAddr, 1), 2);
|
||||
}
|
||||
sOperand = str.toHex(this.getByte(dbgAddr, 1), 2);
|
||||
break;
|
||||
case Debugger.TYPE_SBYTE:
|
||||
sOperand = str.toHex((this.getByte(dbgAddr, 1) << 24) >> 24, 4);
|
||||
|
|
@ -2522,6 +2527,9 @@ if (DEBUGGER) {
|
|||
sOperand = "imm(" + str.toHexWord(type) + ')';
|
||||
break;
|
||||
}
|
||||
if (type & Debugger.TYPE_MEM) {
|
||||
sOperand = '[' + sOperand + ']';
|
||||
}
|
||||
return sOperand;
|
||||
};
|
||||
|
||||
|
|
@ -2536,9 +2544,12 @@ if (DEBUGGER) {
|
|||
*/
|
||||
Debugger.prototype.getRegOperand = function(iReg, type, dbgAddr)
|
||||
{
|
||||
var typeMode = type & Debugger.TYPE_MODE;
|
||||
var typeSize = type & Debugger.TYPE_SIZE;
|
||||
return Debugger.REGS[iReg];
|
||||
/*
|
||||
* Although this breaks with assembler conventions, I'm going to experiment with some different
|
||||
* mnemonics; specifically, "[HL]" instead of "M". This is also more in keeping with how getImmOperand()
|
||||
* displays memory references (ie, by enclosing them in brackets).
|
||||
*/
|
||||
return iReg == Debugger.REG_M? "[HL]" : Debugger.REGS[iReg];
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ var Messages = {
|
|||
BUS: 0x00000040,
|
||||
MEM: 0x00000080,
|
||||
PORT: 0x00000100,
|
||||
TIMER: 0x00000800,
|
||||
CHIPSET: 0x00008000,
|
||||
KEYBOARD: 0x00010000,
|
||||
KEYS: 0x00020000,
|
||||
VIDEO: 0x00040000,
|
||||
|
|
|
|||
|
|
@ -49,10 +49,12 @@ if (NODE) {
|
|||
*
|
||||
* screenWidth: width of the screen canvas, in pixels
|
||||
* screenHeight: height of the screen canvas, in pixels
|
||||
* aspect
|
||||
* rotate
|
||||
* addr
|
||||
* screenColor: background color of the screen canvas (default is black)
|
||||
* aspectRatio
|
||||
* frameBuffer
|
||||
* interruptRate (eg, 120)
|
||||
* refreshRate (eg, 60)
|
||||
* rotation (eg, 90)
|
||||
*
|
||||
* @constructor
|
||||
* @extends Component
|
||||
|
|
@ -65,11 +67,72 @@ if (NODE) {
|
|||
function Video(parmsVideo, canvas, context, textarea, container)
|
||||
{
|
||||
Component.call(this, "Video", parmsVideo, Video, Messages.VIDEO);
|
||||
this.interruptRate = parmsVideo['interruptRate'];
|
||||
this.refreshRate = parmsVideo['refreshRate'] || 60;
|
||||
this.setReady();
|
||||
}
|
||||
|
||||
Component.subclass(Video);
|
||||
|
||||
/**
|
||||
* initBus(cmp, bus, cpu, dbg)
|
||||
*
|
||||
* @this {Video}
|
||||
* @param {Computer} cmp
|
||||
* @param {Bus} bus
|
||||
* @param {CPUSim} cpu
|
||||
* @param {Debugger} dbg
|
||||
*/
|
||||
Video.prototype.initBus = function(cmp, bus, cpu, dbg)
|
||||
{
|
||||
this.cmp = cmp;
|
||||
this.bus = bus;
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
};
|
||||
|
||||
/**
|
||||
* getRefreshRate()
|
||||
*
|
||||
* @this {Video}
|
||||
* @return {number}
|
||||
*/
|
||||
Video.prototype.getRefreshRate = function()
|
||||
{
|
||||
return Math.max(this.refreshRate, this.interruptRate);
|
||||
};
|
||||
|
||||
/**
|
||||
* updateScreen(n)
|
||||
*
|
||||
* Propagates the video buffer to the cell cache and updates the screen with any changes. Forced updates
|
||||
* are generally internal updates triggered by an I/O operation or other state change, while non-forced updates
|
||||
* are the periodic updates coming from the CPU.
|
||||
*
|
||||
* For every cell in the video buffer, compare it to the cell stored in the cell cache, render if it differs,
|
||||
* and then update the cell cache to match. Since initCache() sets every cell in the cell cache to an
|
||||
* invalid value, we're assured that the next call to updateScreen() will redraw the entire (visible) video buffer.
|
||||
*
|
||||
* @this {Video}
|
||||
* @param {number} n (where 0 <= n < getRefreshRate() for a normal update, or -1 for a forced update)
|
||||
*/
|
||||
Video.prototype.updateScreen = function(n)
|
||||
{
|
||||
if (!(n & 1)) {
|
||||
/*
|
||||
* On even updates, call cpu.requestINTR(1), and also update our copy of the screen.
|
||||
*/
|
||||
this.cpu.requestINTR(1);
|
||||
} else {
|
||||
/*
|
||||
* On odd updates, call cpu.requestINTR(2), but do NOT update our copy of the screen, because
|
||||
* the machine has presumably only updated the top half of the frame buffer at this point; it will
|
||||
* update the bottom half of the frame buffer after acknowledging this interrupt.
|
||||
*/
|
||||
this.cpu.requestINTR(2);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Video.init()
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1038,10 +1038,41 @@
|
|||
<xsl:otherwise>false</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="aspectRatio">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@aspect"><xsl:value-of select="@aspect"/></xsl:when>
|
||||
<xsl:when test="@aspectRatio"><xsl:value-of select="@aspectRatio"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="frameBuffer">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@frameBuffer"><xsl:value-of select="@frameBuffer"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="interruptRate">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@interruptRate"><xsl:value-of select="@interruptRate"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="refreshRate">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@refreshRate"><xsl:value-of select="@refreshRate"/></xsl:when>
|
||||
<xsl:otherwise>60</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="rotation">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@rotation"><xsl:value-of select="@rotation"/></xsl:when>
|
||||
<xsl:otherwise>0</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">video</xsl:with-param>
|
||||
<xsl:with-param name="parms">,model:'<xsl:value-of select="$model"/>',mode:<xsl:value-of select="$mode"/>,screenWidth:<xsl:value-of select="$screenWidth"/>,screenHeight:<xsl:value-of select="$screenHeight"/>,memory:<xsl:value-of select="$memory"/>,switches:'<xsl:value-of select="$switches"/>',scale:<xsl:value-of select="$scale"/>,charCols:<xsl:value-of select="$charCols"/>,charRows:<xsl:value-of select="$charRows"/>,fontROM:'<xsl:value-of select="$fontROM"/>',screenColor:'<xsl:value-of select="$screenColor"/>',touchScreen:'<xsl:value-of select="$touchScreen"/>',autoLock:<xsl:value-of select="$autoLock"/></xsl:with-param>
|
||||
<xsl:with-param name="parms">,model:'<xsl:value-of select="$model"/>',mode:<xsl:value-of select="$mode"/>,screenWidth:<xsl:value-of select="$screenWidth"/>,screenHeight:<xsl:value-of select="$screenHeight"/>,memory:<xsl:value-of select="$memory"/>,switches:'<xsl:value-of select="$switches"/>',scale:<xsl:value-of select="$scale"/>,charCols:<xsl:value-of select="$charCols"/>,charRows:<xsl:value-of select="$charRows"/>,fontROM:'<xsl:value-of select="$fontROM"/>',screenColor:'<xsl:value-of select="$screenColor"/>',touchScreen:'<xsl:value-of select="$touchScreen"/>',autoLock:<xsl:value-of select="$autoLock"/>,aspectRatio:<xsl:value-of select="$aspectRatio"/>,frameBuffer:<xsl:value-of select="$frameBuffer"/>,interruptRate:<xsl:value-of select="$interruptRate"/>,refreshRate:<xsl:value-of select="$refreshRate"/>,rotation:<xsl:value-of select="$rotation"/></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue