Black Book demo L23-1 works now

This commit is contained in:
Jeff Parsons 2015-06-19 11:01:51 -07:00
commit ef3873da6e
4 changed files with 158 additions and 60 deletions

View file

@ -3099,8 +3099,21 @@ if (DEBUGGER) {
*/ */
var fBreak = false; var fBreak = false;
if (!this.nBreakSuppress++) { if (!this.nBreakSuppress++) {
addr = this.mapBreakpoint(addr); addr = this.mapBreakpoint(addr);
for (var i = 1; i < aBreak.length; i++) {
/*
* As discussed in opINT3(), I decided to check for INT3 instructions here: we'll tell the CPU to
* stop on INT3 whenever both the INT and HALT message bits are set; a simple "g" command allows you
* to continue.
*/
if (this.messageEnabled(Messages.INT | Messages.HALT)) {
if (this.cpu.probeAddr(addr) == X86.OPCODE.INT3) {
fBreak = true;
}
}
for (var i = 1; !fBreak && i < aBreak.length; i++) {
var dbgAddrBreak = aBreak[i]; var dbgAddrBreak = aBreak[i];
@ -3129,7 +3142,6 @@ if (DEBUGGER) {
this.println("breakpoint hit: " + this.hexAddr(dbgAddrBreak) + " (" + aBreak[0] + ")"); this.println("breakpoint hit: " + this.hexAddr(dbgAddrBreak) + " (" + aBreak[0] + ")");
} }
fBreak = true; fBreak = true;
break;
} }
} }
} }

View file

@ -975,11 +975,11 @@ function Card(video, iCard, data, cbMemory)
var monitorSpecs = Video.monitorSpecs[nMonitorType] || Video.monitorSpecs[ChipSet.MONITOR.MONO]; var monitorSpecs = Video.monitorSpecs[nMonitorType] || Video.monitorSpecs[ChipSet.MONITOR.MONO];
var nCyclesPerSecond = video.cpu.getCyclesPerSecond(); // eg, 4772727 var nCyclesPerSecond = video.cpu.getCyclesPerSecond(); // eg, 4772727
this.nCyclesHorzPeriod = (nCyclesPerSecond / monitorSpecs.nHorzPeriodsPerSec) | 0; this.nCyclesHorzPeriod = (nCyclesPerSecond / monitorSpecs.nHorzPeriodsPerSec)|0;
this.nCyclesHorzActive = (this.nCyclesHorzPeriod * monitorSpecs.percentHorzActive / 100) | 0; this.nCyclesHorzActive = (this.nCyclesHorzPeriod * monitorSpecs.percentHorzActive / 100)|0;
this.nCyclesVertPeriod = this.nCyclesHorzPeriod * monitorSpecs.nHorzPeriodsPerFrame; this.nCyclesVertPeriod = (this.nCyclesHorzPeriod * monitorSpecs.nHorzPeriodsPerFrame)|0;
this.nCyclesVertActive = (this.nCyclesVertPeriod * monitorSpecs.percentVertActive / 100) | 0; this.nCyclesVertActive = (this.nCyclesVertPeriod * monitorSpecs.percentVertActive / 100)|0;
this.nInitCycles = (data[7] == null? 0 : data[7]); this.nInitCycles = (data[7] || 0);
} }
} }
@ -2031,12 +2031,13 @@ Card.prototype.initEGA = function(data, nMonitorType)
/*22*/ 0, /*22*/ 0,
/*23*/ 0, /*23*/ 0,
/*24*/ 0, /*24*/ 0,
/*25*/ Card.VGA_ENABLE.ENABLED, /*25*/ 0,
/*26*/ Card.DAC.MASK.DEFAULT, /*26*/ Card.VGA_ENABLE.ENABLED,
/*27*/ 0, /*27*/ Card.DAC.MASK.DEFAULT,
/*28*/ 0, /*28*/ 0,
/*29*/ Card.DAC.STATE.MODE_WRITE, /*29*/ 0,
/*30*/ new Array(Card.DAC.TOTAL_REGS) /*30*/ Card.DAC.STATE.MODE_WRITE,
/*31*/ new Array(Card.DAC.TOTAL_REGS)
]; ];
} }
@ -2112,14 +2113,15 @@ Card.prototype.initEGA = function(data, nMonitorType)
this.nSetMapBits = data[22]; this.nSetMapBits = data[22];
this.nColorCompare = data[23]; this.nColorCompare = data[23];
this.nColorDontCare = data[24]; this.nColorDontCare = data[24];
this.nStartAddress = data[25]; // this is the last CRTC start address latched from CRTC.START_ADDR_HI,CRTC.START_ADDR_LO
if (this.nCard == Video.CARD.VGA) { if (this.nCard == Video.CARD.VGA) {
this.regVGAEnable = data[25]; this.regVGAEnable = data[26];
this.regDACMask = data[26]; this.regDACMask = data[27];
this.regDACAddr = data[27]; this.regDACAddr = data[28];
this.regDACShift = data[28]; this.regDACShift = data[29];
this.regDACState = data[29]; this.regDACState = data[30];
this.regDACData = data[30]; this.regDACData = data[31];
} }
}; };
@ -2181,14 +2183,15 @@ Card.prototype.saveEGA = function()
data[22] = this.nSetMapBits; data[22] = this.nSetMapBits;
data[23] = this.nColorCompare; data[23] = this.nColorCompare;
data[24] = this.nColorDontCare; data[24] = this.nColorDontCare;
data[25] = this.nStartAddress;
if (this.nCard == Video.CARD.VGA) { if (this.nCard == Video.CARD.VGA) {
data[25] = this.regVGAEnable; data[26] = this.regVGAEnable;
data[26] = this.regDACMask; data[27] = this.regDACMask;
data[27] = this.regDACAddr; data[28] = this.regDACAddr;
data[28] = this.regDACShift; data[29] = this.regDACShift;
data[29] = this.regDACState; data[30] = this.regDACState;
data[30] = this.regDACData; data[31] = this.regDACData;
} }
return data; return data;
}; };
@ -4625,12 +4628,14 @@ Video.prototype.updateScreen = function(fForce)
* unless fForce is set. * unless fForce is set.
*/ */
var fEnabled = false; var fEnabled = false;
if (this.cardActive) { var card = this.cardActive;
if (this.cardActive === this.cardEGA) {
if (this.cardEGA.regATCIndx & Card.ATC.INDX_PAL_ENABLE) fEnabled = true; if (card) {
if (card !== this.cardEGA) {
if (card.regMode & Card.CGA.MODE.VIDEO_ENABLE) fEnabled = true;
} }
else { else {
if (this.cardActive.regMode & Card.CGA.MODE.VIDEO_ENABLE) fEnabled = true; if (card.regATCIndx & Card.ATC.INDX_PAL_ENABLE) fEnabled = true;
} }
} }
@ -4668,9 +4673,25 @@ Video.prototype.updateScreen = function(fForce)
* to follow. FYI, in these calculations, offScreen does not refer to "off-screen" memory, * to follow. FYI, in these calculations, offScreen does not refer to "off-screen" memory,
* but rather the "offset" of the start of visible screen memory. * but rather the "offset" of the start of visible screen memory.
*/ */
var addrScreen = this.cardActive.addrBuffer; var addrScreen = card.addrBuffer;
var addrScreenLimit = addrScreen + this.cardActive.sizeBuffer; var addrScreenLimit = addrScreen + card.sizeBuffer;
var offScreen = ((this.cardActive.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + this.cardActive.regCRTData[Card.CRTC.START_ADDR_LO])|0;
/*
* HACK: nStartAddress is supposed to be "latched" ONLY at the start of every VERT_RETRACE interval;
* this is an attempt to honor that behavior, but unfortunately, updateScreen() is currently called at
* the CPU's discretion, not necessarily in sync with nCyclesVertPeriod. As a result, we must rely
* on other "triggers" to update our latched CRTC start address (eg, see outATC()).
*
* TODO: Consider matching the CPU's nCyclesNextVideoUpdate to the card's nCyclesVertPeriod, ensuring
* that CPU bursts are in sync with VERT_RETRACE. Note, however, that that will be complicated by other
* factors, such as the horizontal retrace interval, and the timing requirements of other cards in a
* multi-display configuration.
*/
if (this.getRetraceBits(card) & Card.CGA.STATUS.VERT_RETRACE) {
card.nStartAddress = ((card.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + card.regCRTData[Card.CRTC.START_ADDR_LO])|0;
}
var offScreen = card.nStartAddress;
/* /*
* Any screen (aka "page") offset must be doubled for text modes, due to the attribute bytes. * Any screen (aka "page") offset must be doubled for text modes, due to the attribute bytes.
@ -4682,14 +4703,14 @@ Video.prototype.updateScreen = function(fForce)
addrScreen += offScreen; addrScreen += offScreen;
var cbScreen = this.cbScreen; var cbScreen = this.cbScreen;
if (this.nCard >= Video.CARD.EGA && this.cardActive.regCRTData[Card.CRTC.EGA.OFFSET]) { if (this.nCard >= Video.CARD.EGA && card.regCRTData[Card.CRTC.EGA.OFFSET]) {
/* /*
* Pre-EGA, the extent of visible screen memory (cbScreen) was derived from nCols * nRows, but since * Pre-EGA, the extent of visible screen memory (cbScreen) was derived from nCols * nRows, but since
* then, the logical width of screen memory (nColsLogical) can differ from the visible width (nCols). * then, the logical width of screen memory (nColsLogical) can differ from the visible width (nCols).
* We now calculate the logical width, and the compute a new cbScreen in much the same way the original * We now calculate the logical width, and the compute a new cbScreen in much the same way the original
* cbScreen was computed (but without any CGA-related padding considerations). * cbScreen was computed (but without any CGA-related padding considerations).
*/ */
this.nColsLogical = this.cardActive.regCRTData[Card.CRTC.EGA.OFFSET] << (this.nFont? 1 : 4); this.nColsLogical = card.regCRTData[Card.CRTC.EGA.OFFSET] << (this.nFont? 1 : 4);
cbScreen = ((((this.nColsLogical * (this.nRows-1) + this.nCols) / this.nCellsPerWord) << 1) + this.cbPadding)|0; cbScreen = ((((this.nColsLogical * (this.nRows-1) + this.nCols) / this.nCellsPerWord) << 1) + this.cbPadding)|0;
} }
@ -5010,6 +5031,48 @@ Video.prototype.updateScreenGraphicsEGA = function(addrScreen, addrScreenLimit)
} }
}; };
/**
* getRetraceBits(card)
*
* This returns a byte value with two bits set or clear as appropriate: DISP_RETRACE and VERT_RETRACE.
*
* @this {Video}
* @param {Object} card
* @return {number}
*/
Video.prototype.getRetraceBits = function(card)
{
var b = 0;
/*
* NOTE: The CGA bits CGA.STATUS.DISP_RETRACE (0x01) and CGA.STATUS.VERT_RETRACE (0x08) match the EGA definitions,
* and they also correspond to the MDA bits MDA.STATUS.HDRIVE (0x01) and MDA.STATUS.BWVIDEO (0x08); I'm not sure why
* the MDA uses different designations, but the bits appear to serve the same purpose.
*
* TODO: Decide whether this more faithful emulation of the retrace bits should be extended to the MDA/CGA, too;
* doing so might slow down the BIOS scroll code a bit, though.
*/
var nCycles = this.cpu.getCycles();
var nElapsedCycles = nCycles - card.nInitCycles;
if (nElapsedCycles < 0) nElapsedCycles = 0; // TODO: Determine if this ever happens
var nCyclesHorzRemain = nElapsedCycles % card.nCyclesHorzPeriod;
if (nCyclesHorzRemain > card.nCyclesHorzActive) b |= Card.CGA.STATUS.DISP_RETRACE;
var nCyclesVertRemain = nElapsedCycles % card.nCyclesVertPeriod;
if (nCyclesVertRemain > card.nCyclesVertActive) b |= Card.CGA.STATUS.VERT_RETRACE | Card.CGA.STATUS.DISP_RETRACE;
/*
* This is optional: the number of CPU cycles that remain in the current vertical period is all we need to keep
* track of (the number of cycles since the card was initialized is fine, too, but that delta can become extremely
* large after a while).
*
* card.nInitCycles = nCycles - nCyclesVertRemain;
*
* NOTE: Now that we're calling getRetraceBits() more frequently (ie, for internal checks), resetting nInitCycles
* in this fashion preserves the vertical period at the expense of the horizontal period, which in turn can cause
* grief in ROM BIOS code that requires strict horizontal retrace times. So the above code is now disabled.
*/
return b;
};
/** /**
* inMDAIndx(port, addrFrom) * inMDAIndx(port, addrFrom)
* *
@ -5162,6 +5225,11 @@ Video.prototype.outATC = function(port, bOut, addrFrom)
this.updateScreen(true); this.updateScreen(true);
} }
} }
/*
* HACK: nStartAddress is supposed to be "latched" ONLY at the start of every VERT_RETRACE interval,
* but other "triggers" are currently required; see updateScreen() for details.
*/
this.cardEGA.nStartAddress = ((this.cardEGA.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + this.cardEGA.regCRTData[Card.CRTC.START_ADDR_LO])|0;
} else { } else {
var iReg = this.cardEGA.regATCIndx & Card.ATC.INDX_MASK; var iReg = this.cardEGA.regATCIndx & Card.ATC.INDX_MASK;
if (iReg >= Card.ATC.PALETTE_REGS || !fPalEnabled) { if (iReg >= Card.ATC.PALETTE_REGS || !fPalEnabled) {
@ -5873,6 +5941,15 @@ Video.prototype.outCRTCData = function(card, port, bOut, addrFrom)
} }
card.regCRTData[card.regCRTIndx] = bOut; card.regCRTData[card.regCRTIndx] = bOut;
} }
if (card.regCRTIndx == Card.CRTC.START_ADDR_HI || card.regCRTIndx == Card.CRTC.START_ADDR_LO) {
/*
* HACK: nStartAddress is supposed to be "latched" ONLY at the start of every VERT_RETRACE interval,
* but the best we can currently do is latch it during retrace, as well as other times (eg, see outATC()).
*/
if (this.getRetraceBits(card) & Card.CGA.STATUS.DISP_RETRACE) {
card.nStartAddress = ((card.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + card.regCRTData[Card.CRTC.START_ADDR_LO])|0;
}
}
/* /*
* During mode changes on the EGA, all the CRTC regs are typically programmed in sequence, * During mode changes on the EGA, all the CRTC regs are typically programmed in sequence,
* and if that's all that's happening with Card.CRTC.MAX_SCAN_LINE, then we don't want to treat * and if that's all that's happening with Card.CRTC.MAX_SCAN_LINE, then we don't want to treat
@ -5938,29 +6015,7 @@ Video.prototype.outCardMode = function(card, bOut, addrFrom)
*/ */
Video.prototype.inCardStatus = function(card, addrFrom) Video.prototype.inCardStatus = function(card, addrFrom)
{ {
var b = 0; var b = this.getRetraceBits(card);
/*
* NOTE: The CGA bits CGA.STATUS.DISP_RETRACE (0x01) and CGA.STATUS.VERT_RETRACE (0x08) match the EGA definitions,
* and they also correspond to the MDA bits MDA.STATUS.HDRIVE (0x01) and MDA.STATUS.BWVIDEO (0x08); I'm not sure why
* the MDA uses different designations, but the bits appear to serve the same purpose.
*
* TODO: Decide whether this more faithful emulation of the retrace bits should be extended to the MDA/CGA, too;
* doing so might slow down the BIOS scroll code a bit, though.
*/
var nCycles = this.cpu.getCycles();
var nElapsedCycles = nCycles - card.nInitCycles;
if (nElapsedCycles < 0) nElapsedCycles = 0; // TODO: Determine if this ever happens
var nCyclesHorzRemain = nElapsedCycles % card.nCyclesHorzPeriod;
if (nCyclesHorzRemain > card.nCyclesHorzActive) b |= Card.CGA.STATUS.DISP_RETRACE;
var nCyclesVertRemain = nElapsedCycles % card.nCyclesVertPeriod;
if (nCyclesVertRemain > card.nCyclesVertActive) b |= Card.CGA.STATUS.VERT_RETRACE;
/*
* This is optional: the number of CPU cycles that remain in the current vertical period is all we need to keep
* track of (the number of cycles since the card was initialized is fine, too, but that delta can become extremely
* large after a while).
*/
card.nInitCycles = nCycles - nCyclesVertRemain;
if (card === this.cardEGA) { if (card === this.cardEGA) {
/* /*
@ -6001,9 +6056,13 @@ Video.prototype.inCardStatus = function(card, addrFrom)
* *
* Also, according to http://www.seasip.info/VintagePC/mda.html, on an MDA, bits 7-4 are always ON and * Also, according to http://www.seasip.info/VintagePC/mda.html, on an MDA, bits 7-4 are always ON and
* bits 2-1 are always OFF, hence the "OR" of 0xf0. * bits 2-1 are always OFF, hence the "OR" of 0xf0.
*
* TODO: Decide whether to preserve the bits from getRetraceBits() on the MDA/CGA; we're continuing
* to do a simple toggle, partly on the theory that that may speed up the CGA BIOS scroll code a bit.
*/ */
b = (card.regStatus ^= (Card.CGA.STATUS.DISP_RETRACE | Card.CGA.STATUS.VERT_RETRACE)) | 0xf0; b = (card.regStatus ^= (Card.CGA.STATUS.DISP_RETRACE | Card.CGA.STATUS.VERT_RETRACE)) | 0xf0;
} }
card.regStatus = b; card.regStatus = b;
this.printMessageIO(card.port + 6, null, addrFrom, (card === this.cardEGA? "STATUS1" : "STATUS"), b); this.printMessageIO(card.port + 6, null, addrFrom, (card === this.cardEGA? "STATUS1" : "STATUS"), b);
return b; return b;

View file

@ -3576,7 +3576,7 @@ X86.fnSrcNone = function SrcNone()
}; };
/** /**
* fnFault(nFault, nError, fHalt) * fnFault(nFault, nError, fHalt, nCycles)
* *
* Helper to dispatch faults. * Helper to dispatch faults.
* *
@ -3584,8 +3584,9 @@ X86.fnSrcNone = function SrcNone()
* @param {number} nFault * @param {number} nFault
* @param {number} [nError] (if omitted, no error code will be pushed) * @param {number} [nError] (if omitted, no error code will be pushed)
* @param {boolean} [fHalt] will halt the CPU if true *and* a Debugger is loaded * @param {boolean} [fHalt] will halt the CPU if true *and* a Debugger is loaded
* @param {number} [nCycles] cycle count to pass through to fnINT(), if any
*/ */
X86.fnFault = function(nFault, nError, fHalt) X86.fnFault = function(nFault, nError, fHalt, nCycles)
{ {
if (!this.aFlags.fComplete) { if (!this.aFlags.fComplete) {
this.printMessage("Fault " + str.toHexByte(nFault) + " blocked by Debugger", Messages.WARN); this.printMessage("Fault " + str.toHexByte(nFault) + " blocked by Debugger", Messages.WARN);
@ -3623,7 +3624,7 @@ X86.fnFault = function(nFault, nError, fHalt)
fDispatch = false; fDispatch = false;
} }
if (fDispatch) X86.fnINT.call(this, this.nFault = nFault, nError, 0); if (fDispatch) X86.fnINT.call(this, this.nFault = nFault, nError, nCycles || 0);
/* /*
* Since this fault is likely being issued in the context of an instruction that hasn't finished * Since this fault is likely being issued in the context of an instruction that hasn't finished
@ -3696,7 +3697,7 @@ X86.fnFaultMessage = function(nFault, nError, fHalt)
* *
* When a triple fault shows up, nFault is -1; it displays as 0xff only because we use toHexByte(). * When a triple fault shows up, nFault is -1; it displays as 0xff only because we use toHexByte().
*/ */
if (bOpcode == X86.OPCODE.INT3) { if (bOpcode == X86.OPCODE.INT3 && !this.addrIDTLimit) {
fHalt = false; fHalt = false;
bitsMessage |= Messages.CPU; bitsMessage |= Messages.CPU;
} }

View file

@ -3322,6 +3322,32 @@ X86.opRETF = function RETF()
*/ */
X86.opINT3 = function INT3() X86.opINT3 = function INT3()
{ {
/*
* To give our own Debugger the ability to stop execution on INT3, I thought about treating this as
* a fault rather than an interrupt, in order to leverage the existing Debugger logic inside fnFault()
* processing, but that has the unwanted side-effect of rewinding EIP to the INT3 prior to issuing
* the interrupt, and the corresponding IRET takes us right back to the INT3.
*
* X86.fnFault.call(this, X86.EXCEPTION.BREAKPOINT, null, false, this.cycleCounts.nOpCyclesInt3D);
*
* Then I had the idea of using the fnFaultMessage() function, in much the same way that fnFault()
* does for actual faults: if the user turned on the FAULT and HALT message bits, then fnFaultMessage()
* would tell us to halt; otherwise, we'd perform the normal fnINT() call.
*
* if (X86.fnFaultMessage.call(this, X86.EXCEPTION.BREAKPOINT)) {
* this.setIP(this.opLIP - this.segCS.base);
* return;
* }
*
* However, that makes it a little tedious to get past the INT3 (you have to use a Debugger command
* like "t;g"), and a somewhat confusing fault message is displayed; eg:
*
* Fault 0x03 on opcode 0xB4 at 09CE:0155 (%009E35)
*
* The best solution was to leave this function alone, and change the Debugger's checkBreakpoint()
* function to stop execution on INT3 whenever both the INT and HALT message bits are set; a simple "g"
* command allows you to continue.
*/
X86.fnINT.call(this, X86.EXCEPTION.BREAKPOINT, null, this.cycleCounts.nOpCyclesInt3D); X86.fnINT.call(this, X86.EXCEPTION.BREAKPOINT, null, this.cycleCounts.nOpCyclesInt3D);
}; };