From ef3873da6e4a7e951b045301b9279a4f3fa97c0c Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Fri, 19 Jun 2015 11:01:51 -0700 Subject: [PATCH] Black Book demo L23-1 works now --- modules/pcjs/lib/debugger.js | 16 +++- modules/pcjs/lib/video.js | 167 ++++++++++++++++++++++++----------- modules/pcjs/lib/x86func.js | 9 +- modules/pcjs/lib/x86ops.js | 26 ++++++ 4 files changed, 158 insertions(+), 60 deletions(-) diff --git a/modules/pcjs/lib/debugger.js b/modules/pcjs/lib/debugger.js index 2d2f3d2b0..acdea73e0 100644 --- a/modules/pcjs/lib/debugger.js +++ b/modules/pcjs/lib/debugger.js @@ -3099,8 +3099,21 @@ if (DEBUGGER) { */ var fBreak = false; if (!this.nBreakSuppress++) { + 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]; @@ -3129,7 +3142,6 @@ if (DEBUGGER) { this.println("breakpoint hit: " + this.hexAddr(dbgAddrBreak) + " (" + aBreak[0] + ")"); } fBreak = true; - break; } } } diff --git a/modules/pcjs/lib/video.js b/modules/pcjs/lib/video.js index c70a8735b..4e9cf190f 100644 --- a/modules/pcjs/lib/video.js +++ b/modules/pcjs/lib/video.js @@ -975,11 +975,11 @@ function Card(video, iCard, data, cbMemory) var monitorSpecs = Video.monitorSpecs[nMonitorType] || Video.monitorSpecs[ChipSet.MONITOR.MONO]; var nCyclesPerSecond = video.cpu.getCyclesPerSecond(); // eg, 4772727 - this.nCyclesHorzPeriod = (nCyclesPerSecond / monitorSpecs.nHorzPeriodsPerSec) | 0; - this.nCyclesHorzActive = (this.nCyclesHorzPeriod * monitorSpecs.percentHorzActive / 100) | 0; - this.nCyclesVertPeriod = this.nCyclesHorzPeriod * monitorSpecs.nHorzPeriodsPerFrame; - this.nCyclesVertActive = (this.nCyclesVertPeriod * monitorSpecs.percentVertActive / 100) | 0; - this.nInitCycles = (data[7] == null? 0 : data[7]); + this.nCyclesHorzPeriod = (nCyclesPerSecond / monitorSpecs.nHorzPeriodsPerSec)|0; + this.nCyclesHorzActive = (this.nCyclesHorzPeriod * monitorSpecs.percentHorzActive / 100)|0; + this.nCyclesVertPeriod = (this.nCyclesHorzPeriod * monitorSpecs.nHorzPeriodsPerFrame)|0; + this.nCyclesVertActive = (this.nCyclesVertPeriod * monitorSpecs.percentVertActive / 100)|0; + this.nInitCycles = (data[7] || 0); } } @@ -2031,12 +2031,13 @@ Card.prototype.initEGA = function(data, nMonitorType) /*22*/ 0, /*23*/ 0, /*24*/ 0, - /*25*/ Card.VGA_ENABLE.ENABLED, - /*26*/ Card.DAC.MASK.DEFAULT, - /*27*/ 0, + /*25*/ 0, + /*26*/ Card.VGA_ENABLE.ENABLED, + /*27*/ Card.DAC.MASK.DEFAULT, /*28*/ 0, - /*29*/ Card.DAC.STATE.MODE_WRITE, - /*30*/ new Array(Card.DAC.TOTAL_REGS) + /*29*/ 0, + /*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.nColorCompare = data[23]; 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) { - this.regVGAEnable = data[25]; - this.regDACMask = data[26]; - this.regDACAddr = data[27]; - this.regDACShift = data[28]; - this.regDACState = data[29]; - this.regDACData = data[30]; + this.regVGAEnable = data[26]; + this.regDACMask = data[27]; + this.regDACAddr = data[28]; + this.regDACShift = data[29]; + this.regDACState = data[30]; + this.regDACData = data[31]; } }; @@ -2181,14 +2183,15 @@ Card.prototype.saveEGA = function() data[22] = this.nSetMapBits; data[23] = this.nColorCompare; data[24] = this.nColorDontCare; + data[25] = this.nStartAddress; if (this.nCard == Video.CARD.VGA) { - data[25] = this.regVGAEnable; - data[26] = this.regDACMask; - data[27] = this.regDACAddr; - data[28] = this.regDACShift; - data[29] = this.regDACState; - data[30] = this.regDACData; + data[26] = this.regVGAEnable; + data[27] = this.regDACMask; + data[28] = this.regDACAddr; + data[29] = this.regDACShift; + data[30] = this.regDACState; + data[31] = this.regDACData; } return data; }; @@ -4625,12 +4628,14 @@ Video.prototype.updateScreen = function(fForce) * unless fForce is set. */ var fEnabled = false; - if (this.cardActive) { - if (this.cardActive === this.cardEGA) { - if (this.cardEGA.regATCIndx & Card.ATC.INDX_PAL_ENABLE) fEnabled = true; + var card = this.cardActive; + + if (card) { + if (card !== this.cardEGA) { + if (card.regMode & Card.CGA.MODE.VIDEO_ENABLE) fEnabled = true; } 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, * but rather the "offset" of the start of visible screen memory. */ - var addrScreen = this.cardActive.addrBuffer; - var addrScreenLimit = addrScreen + this.cardActive.sizeBuffer; - var offScreen = ((this.cardActive.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + this.cardActive.regCRTData[Card.CRTC.START_ADDR_LO])|0; + var addrScreen = card.addrBuffer; + var addrScreenLimit = addrScreen + card.sizeBuffer; + + /* + * 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. @@ -4682,14 +4703,14 @@ Video.prototype.updateScreen = function(fForce) addrScreen += offScreen; 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 * 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 * 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; } @@ -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) * @@ -5162,6 +5225,11 @@ Video.prototype.outATC = function(port, bOut, addrFrom) 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 { var iReg = this.cardEGA.regATCIndx & Card.ATC.INDX_MASK; if (iReg >= Card.ATC.PALETTE_REGS || !fPalEnabled) { @@ -5873,6 +5941,15 @@ Video.prototype.outCRTCData = function(card, port, bOut, addrFrom) } 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, * 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) { - 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; - /* - * 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; + var b = this.getRetraceBits(card); 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 * 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; } + card.regStatus = b; this.printMessageIO(card.port + 6, null, addrFrom, (card === this.cardEGA? "STATUS1" : "STATUS"), b); return b; diff --git a/modules/pcjs/lib/x86func.js b/modules/pcjs/lib/x86func.js index ce539c65c..5bf546e28 100644 --- a/modules/pcjs/lib/x86func.js +++ b/modules/pcjs/lib/x86func.js @@ -3576,7 +3576,7 @@ X86.fnSrcNone = function SrcNone() }; /** - * fnFault(nFault, nError, fHalt) + * fnFault(nFault, nError, fHalt, nCycles) * * Helper to dispatch faults. * @@ -3584,8 +3584,9 @@ X86.fnSrcNone = function SrcNone() * @param {number} nFault * @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 {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) { this.printMessage("Fault " + str.toHexByte(nFault) + " blocked by Debugger", Messages.WARN); @@ -3623,7 +3624,7 @@ X86.fnFault = function(nFault, nError, fHalt) 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 @@ -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(). */ - if (bOpcode == X86.OPCODE.INT3) { + if (bOpcode == X86.OPCODE.INT3 && !this.addrIDTLimit) { fHalt = false; bitsMessage |= Messages.CPU; } diff --git a/modules/pcjs/lib/x86ops.js b/modules/pcjs/lib/x86ops.js index 5daf0363a..253703910 100644 --- a/modules/pcjs/lib/x86ops.js +++ b/modules/pcjs/lib/x86ops.js @@ -3322,6 +3322,32 @@ X86.opRETF = function RETF() */ 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); };