Improved EGA/VGA memory access

This commit is contained in:
Jeff Parsons 2015-06-04 18:38:39 -07:00
commit 59002166cd

View file

@ -884,7 +884,7 @@ Video.aEGADWToByte[0x80808080|0] = 0xf;
*
* WARNING: Since Card 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.
* of Component, such as Component.assert(), or methods of the parent (video) object.
*
* @constructor
* @param {Video} [video]
@ -900,6 +900,8 @@ function Card(video, iCard, data, cbMemory)
*/
if (iCard !== undefined && (!data || data.length)) {
this.video = video;
var specs = Video.cardSpecs[iCard];
var nMonitorType = video.nMonitorType || specs[5];
@ -1523,32 +1525,56 @@ if (DEBUGGER) Card.GRC.REGS = ["SRESET","ESRESET","COLORCMP","DATAROT","READMAP"
*/
/*
* Values returned by getAccess(); the low byte describes the current "read mode", while the high byte describes the
* current "write mode".
* Values returned by getAccess(); the high byte describes the read mode, and the low byte describes the write mode.
*
* V2 should never appear in any values used by getAccess() or setAccess()/setMemoryAccess(); the sole purpose of V2 is
* to distinguish newer (V2) access values from older (V1) access values in saved contexts. It's set when the context
* is saved, and cleared when the context is restored. Thus, if V2 is not set on restore, we assume we're dealing with
* a V1 value, so we run it through the V1 table (below) to produce a V2 value. Hopefully at some point V1 contexts
* can be deprecated, and the V2 bit can be eliminated/repurposed.
*/
Card.ACCESS = {
READ: { // READ values are designed to be OR'ed with WRITE values
EVENODD: 0x0001,
MODE0: 0x0002,
MODE1: 0x0010,
MASK: 0x00ff
MODE0: 0x4000,
MODE1: 0x4100,
EVENODD: 0x1000,
MASK: 0xFF00
},
WRITE: { // and WRITE values are designed to be OR'ed with READ values
EVENODD: 0x0100,
MODE0: 0x0200,
MODE0ROT: 0x0400,
MODE0AND: 0x0600,
MODE0OR: 0x0A00,
MODE0XOR: 0x0E00,
MODE1: 0x1000,
MODE2: 0x2000,
MODE2AND: 0x6000,
MODE2OR: 0xA000,
MODE2XOR: 0xE000,
MASK: 0xff00
}
MODE0: 0x0000,
MODE1: 0x0001,
MODE2: 0x0002,
MODE3: 0x0003, // VGA only
EVENODD: 0x0010,
ROT: 0x0020,
AND: 0x0060,
OR: 0x00A0,
XOR: 0x00E0,
MASK: 0x00FB // 0xFB ensures we strip any lingering V2 bit from the value
},
V2: 0x0004 // this is a signature bit used ONLY to differentiate V2 access values from V1
};
/*
* Table of older (V1) access values and their corresponding new values; the new values are similar but a little
* more rational (for example, using common values for all the logical operations across modes).
*/
Card.ACCESS.V1 = [];
Card.ACCESS.V1[0x0002] = Card.ACCESS.READ.MODE0;
Card.ACCESS.V1[0x0003] = Card.ACCESS.READ.MODE0 | Card.ACCESS.READ.EVENODD;
Card.ACCESS.V1[0x0010] = Card.ACCESS.READ.MODE1;
Card.ACCESS.V1[0x0200] = Card.ACCESS.WRITE.MODE0;
Card.ACCESS.V1[0x0400] = Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.ROT;
Card.ACCESS.V1[0x0600] = Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.AND;
Card.ACCESS.V1[0x0A00] = Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.OR;
Card.ACCESS.V1[0x0E00] = Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.XOR;
Card.ACCESS.V1[0x0300] = Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.EVENODD;
Card.ACCESS.V1[0x1000] = Card.ACCESS.WRITE.MODE1;
Card.ACCESS.V1[0x2000] = Card.ACCESS.WRITE.MODE2;
Card.ACCESS.V1[0x6000] = Card.ACCESS.WRITE.MODE2 | Card.ACCESS.WRITE.AND;
Card.ACCESS.V1[0xA000] = Card.ACCESS.WRITE.MODE2 | Card.ACCESS.WRITE.OR;
Card.ACCESS.V1[0xE000] = Card.ACCESS.WRITE.MODE2 | Card.ACCESS.WRITE.XOR;
/**
* readByteMode0(off, addr)
*
@ -1576,7 +1602,8 @@ Card.ACCESS.readByteMode0EvenOdd = function readByteMode0EvenOdd(off, addr)
{
off += this.offset;
var idw = off & ~0x1;
return (!(off & 1)? this.adw[idw] : (this.adw[idw] >> 8)) & 0xff;
var dw = this.controller.latches = this.adw[idw];
return (!(off & 1)? dw : (dw >> 8)) & 0xff;
};
/**
@ -1785,6 +1812,30 @@ Card.ACCESS.writeByteMode1 = function writeByteMode1(off, b, addr)
}
};
/**
* writeByteMode1EvenOdd(off, b, addr)
*
* @this {Memory}
* @param {number} off
* @param {number} b (ignored; the EGA latches provide the source data)
* @param {number} [addr]
*/
Card.ACCESS.writeByteMode1EvenOdd = function writeByteMode1EvenOdd(off, b, addr)
{
off += this.offset;
//
// When even/odd addressing is enabled, nWriteMapMask must be cleared for planes 1 and 3 if
// the address is even, and cleared for planes 0 and 2 if the address is odd.
//
var idw = off & ~0x1;
var maskMaps = this.controller.nWriteMapMask & (idw == off? 0x00ff00ff : (0xff00ff00|0));
var dw = (this.adw[idw] & ~maskMaps) | (this.controller.latches & maskMaps);
if (this.adw[idw] != dw) {
this.adw[idw] = dw;
this.fDirty = true;
}
};
/**
* writeByteMode2(off, b, addr)
*
@ -1872,20 +1923,23 @@ Card.ACCESS.writeByteMode2Xor = function writeByteMode2Xor(off, b, addr)
* Mappings from getAccess() values to access functions above
*/
Card.ACCESS.afn = [];
Card.ACCESS.afn[Card.ACCESS.READ.MODE0] = Card.ACCESS.readByteMode0;
Card.ACCESS.afn[Card.ACCESS.READ.MODE0 | Card.ACCESS.READ.EVENODD] = Card.ACCESS.readByteMode0EvenOdd;
Card.ACCESS.afn[Card.ACCESS.READ.MODE1] = Card.ACCESS.readByteMode1;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE0] = Card.ACCESS.writeByteMode0;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE0ROT] = Card.ACCESS.writeByteMode0Rot;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE0AND] = Card.ACCESS.writeByteMode0And;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE0OR] = Card.ACCESS.writeByteMode0Or;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE0XOR] = Card.ACCESS.writeByteMode0Xor;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.EVENODD] = Card.ACCESS.writeByteMode0EvenOdd;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE1] = Card.ACCESS.writeByteMode1;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE2] = Card.ACCESS.writeByteMode2;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE2AND] = Card.ACCESS.writeByteMode2And;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE2OR] = Card.ACCESS.writeByteMode2Or;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE2XOR] = Card.ACCESS.writeByteMode2Xor;
Card.ACCESS.afn[Card.ACCESS.READ.MODE0] = Card.ACCESS.readByteMode0;
Card.ACCESS.afn[Card.ACCESS.READ.MODE0 | Card.ACCESS.READ.EVENODD] = Card.ACCESS.readByteMode0EvenOdd;
Card.ACCESS.afn[Card.ACCESS.READ.MODE1] = Card.ACCESS.readByteMode1;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE0] = Card.ACCESS.writeByteMode0;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.ROT] = Card.ACCESS.writeByteMode0Rot;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.AND] = Card.ACCESS.writeByteMode0And;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.OR] = Card.ACCESS.writeByteMode0Or;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.XOR] = Card.ACCESS.writeByteMode0Xor;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.EVENODD] = Card.ACCESS.writeByteMode0EvenOdd;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE1] = Card.ACCESS.writeByteMode1;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE1 | Card.ACCESS.WRITE.EVENODD] = Card.ACCESS.writeByteMode1EvenOdd;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE2] = Card.ACCESS.writeByteMode2;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE2 | Card.ACCESS.WRITE.AND] = Card.ACCESS.writeByteMode2And;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE2 | Card.ACCESS.WRITE.OR] = Card.ACCESS.writeByteMode2Or;
Card.ACCESS.afn[Card.ACCESS.WRITE.MODE2 | Card.ACCESS.WRITE.XOR] = Card.ACCESS.writeByteMode2Xor;
/**
* initEGA(data)
@ -1937,7 +1991,7 @@ Card.prototype.initEGA = function(data, nMonitorType)
* characters and attributes are typically stored (ie, in planes 0 and 1, respectively). As soon as the machine
* starts up and initializes the hardware itself, these defaults won't matter.
*/
/*15*/ Card.ACCESS.READ.MODE0 | Card.ACCESS.READ.EVENODD | Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.EVENODD,
/*15*/ Card.ACCESS.READ.MODE0 | Card.ACCESS.READ.EVENODD | Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.EVENODD | Card.ACCESS.V2,
/*16*/ 0,
/*17*/ 0xffffffff|0,
/*18*/ 0,
@ -1989,14 +2043,24 @@ Card.prototype.initEGA = function(data, nMonitorType)
}
this.addrBuffer = a[0];
this.sizeBuffer = a[1];
Component.assert(this.cbMemory === a[2]);
this.video.assert(this.cbMemory === a[2]);
var cdw = this.cbMemory >> 2;
this.adwMemory = data[14];
if (this.adwMemory && this.adwMemory.length < cdw) {
this.adwMemory = State.decompressEvenOdd(this.adwMemory, cdw);
}
this.setMemoryAccess(data[15]);
var nAccess = data[15];
if (nAccess) {
if (nAccess & Card.ACCESS.V2) {
nAccess &= ~Card.ACCESS.V2;
} else {
this.video.assert(Card.ACCESS.V1[nAccess & 0xff00] !== undefined && Card.ACCESS.V1[nAccess & 0xff] !== undefined);
nAccess = Card.ACCESS.V1[nAccess & 0xff00] | Card.ACCESS.V1[nAccess & 0xff];
}
}
this.setMemoryAccess(nAccess);
/*
* nReadMapShift must perfectly track how the GRC.READMAP register is programmed, so that Card.ACCESS.READ.MODE0
@ -2077,7 +2141,7 @@ Card.prototype.saveEGA = function()
data[12] = this.latches;
data[13] = [this.addrBuffer, this.sizeBuffer, this.cbMemory];
data[14] = State.compressEvenOdd(this.adwMemory);
data[15] = this.nAccess;
data[15] = this.nAccess | Card.ACCESS.V2;
data[16] = this.nReadMapShift;
data[17] = this.nWriteMapMask;
data[18] = this.nDataRotate;
@ -3731,16 +3795,16 @@ Video.prototype.getAccess = function()
switch (nWriteMode) {
case Card.GRC.MODE.WRITE_MODE0:
if (regDataRotate) {
nWriteAccess = Card.ACCESS.WRITE.MODE0ROT;
nWriteAccess = Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.ROT;
switch (regDataRotate & Card.GRC.DATAROT.FUNC) {
case Card.GRC.DATAROT.AND:
nWriteAccess = Card.ACCESS.WRITE.MODE0AND;
nWriteAccess = Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.AND;
break;
case Card.GRC.DATAROT.OR:
nWriteAccess = Card.ACCESS.WRITE.MODE0OR;
nWriteAccess = Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.OR;
break;
case Card.GRC.DATAROT.XOR:
nWriteAccess = Card.ACCESS.WRITE.MODE0XOR;
nWriteAccess = Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.XOR;
break;
default:
break;
@ -3757,13 +3821,13 @@ Video.prototype.getAccess = function()
nWriteAccess = Card.ACCESS.WRITE.MODE2;
break;
case Card.GRC.DATAROT.AND:
nWriteAccess = Card.ACCESS.WRITE.MODE2AND;
nWriteAccess = Card.ACCESS.WRITE.MODE2 | Card.ACCESS.WRITE.AND;
break;
case Card.GRC.DATAROT.OR:
nWriteAccess = Card.ACCESS.WRITE.MODE2OR;
nWriteAccess = Card.ACCESS.WRITE.MODE2 | Card.ACCESS.WRITE.OR;
break;
case Card.GRC.DATAROT.XOR:
nWriteAccess = Card.ACCESS.WRITE.MODE2XOR;
nWriteAccess = Card.ACCESS.WRITE.MODE2 | Card.ACCESS.WRITE.XOR;
break;
}
break;
@ -3794,7 +3858,7 @@ Video.prototype.getAccess = function()
Video.prototype.setAccess = function(nAccess)
{
var card = this.cardActive;
if (nAccess != null && card && nAccess != card.nAccess) {
if (card && nAccess != null && nAccess != card.nAccess) {
if (DEBUG && this.messageEnabled()) {
this.printMessage("setAccess(" + str.toHexWord(nAccess) + ")");
@ -4955,8 +5019,9 @@ Video.prototype.inStatus0 = function(port, addrFrom)
*
* and writes the first 3 bytes to DAC register #0, and then compares SWSENSE to the 4th byte (0x10).
*
* If the 4th byte matches (and I think it should), then the ROM clears the BIOS "monochrome monitor" bit,
* and does the same thing with 5 more arrays:
* If the 4th byte matches, then the ROM clears the BIOS "monochrome monitor" bit, and does the same
* thing again with 5 more arrays, expecting the 4th byte in all 5 arrays to match SWSENSE, and being
* very unhappy if they don't:
*
* db 0x14,0x14,0x14,0x10
* db 0x2D,0x14,0x14,0x00
@ -4964,14 +5029,10 @@ Video.prototype.inStatus0 = function(port, addrFrom)
* db 0x14,0x14,0x2D,0x00
* db 0x2D,0x2D,0x2D,0x00
*
* I've not found any documentation that explains how the SWSENSE bit should reflect changes to the DAC
* in relation to the type of monitor, but it's clear from the ROM BIOS that all 5 of the 4th bytes must
* match SWSENSE after each DAC change, or we get error beeps.
* So I ensure happiness by setting SWSENSE unless any of the three 6-bit DAC values contain 0x2D.
*
* So I will force that result by clearing SWSENSE if any of the three 6-bit DAC values contain 0x2D, and
* setting it otherwise. This hard-coded behavior assumes a color monitor. If you really want to simulate
* a monochrome monitor, then first array will have to miscompare, and the 4th byte of the following arrays
* must match instead:
* This hard-coded behavior assumes a color monitor. If you really want to simulate a monochrome monitor,
* then the 1st array (above) must mismatch, and a different set of arrays must all match:
*
* db 0x04,0x12,0x04,0x10
* db 0x1E,0x12,0x04,0x00
@ -4979,8 +5040,8 @@ Video.prototype.inStatus0 = function(port, addrFrom)
* db 0x04,0x16,0x15,0x00
* db 0x00,0x00,0x00,0x10
*
* In other words, for the monochrome monitor case, set SWSENSE only when DAC register #0 matches the
* first and last rows.
* In other words, for a monochrome monitor, set SWSENSE only when DAC register #0 matches the first and last
* sets of values.
*/
var dwDAC = this.cardEGA.regDACData[0];
if ((dwDAC & 0x3f) != 0x2d && (dwDAC & (0x3f << 6)) != (0x2d << 6) && (dwDAC & (0x3f << 12)) != (0x2d << 12)) {