VT100 SET-UP screen is accessible now

This commit is contained in:
Jeff Parsons 2016-08-14 10:52:24 -07:00
commit e7324a5c6a
10 changed files with 5989 additions and 5856 deletions

View file

@ -169,13 +169,29 @@ ChipSet.SI1978 = {
/*
* One of the many chips in the VT100 is an 8224, which operates at 24.8832MHz. That frequency is divided by 9
* to yield a 361.69ns clock period for the 8080 CPU, which means the CPU is running at 2.76Mhz (cycles per second).
* Hence the CPU component in the VT100's machine.xml is defined as:
* to yield a 361.69ns clock period for the 8080 CPU, which means (in theory) that the CPU is running at 2.76Mhz.
*
* Hence the CPU component in the VT100's machine.xml SHOULD be defined as:
*
* <cpu id="cpu8080" model="8080" cycles="2764800"/>
*
* where 2764800 = 24883200 / 9. You need to know this because we rely on the CPU frequency for simulating some
* of the other VT100 circuits.
* where 2764800 = 24883200 / 9. Unfortunately, the VT100 ROM decrements a countdown value in memory to determine
* cursor blink rate, and if we use 2764800 cycles per second, the cursor blinks MUCH too fast. It's surprising that
* the VT100 doesn't rely on vertical retrace interrupts for blink rate. Perhaps the designers were concerned about
* consistency across 60Hz and 50Hz display modes, although that seems like a minor concern, considering that the
* alternative means the ROM is now tied to a specific CPU operating frequency. However, short of rewriting portions
* of the ROM, we have to deal with it.
*
* And we deal with it by lowering cycles per second to 1000000 (1Mhz). I'm guessing that in a real VT100, the 8080
* gets bogged down by other factors (eg, the Video Processor's DMA requests), but we don't simulate the hardware to
* that level of detail, so the easiest solution is to lower the effective clock speed.
*
* NOTE: If you've noticed that the VT100 cursor blinks unevenly, you're right, and it's by design: the ROM uses a
* countdown value for the cursor's "on" state that is twice as large as that for the cursor's "off" state, so it's
* "on" twice as long as it's "off".
*
* WARNING: The choice of clock speed has an effect on other simulated VT100 circuits; see the DC011 Timing Chip
* discussion below, along with the getVT100LBA() function.
*
* For reference, here is a list of all the VT100 I/O ports, from /devices/pc8080/machine/vt100/debugger/README.md,
* which in turn comes from p. 4-17 of the VT100 Technical Manual (July 1982):
@ -729,7 +745,7 @@ ChipSet.prototype.outSIWatchdog = function(port, b, addrFrom)
};
/**
* getVT100LBA(nBit)
* getVT100LBA(iBit)
*
* Returns the state of the requested (simulated) LBA bit.
*
@ -738,12 +754,12 @@ ChipSet.prototype.outSIWatchdog = function(port, b, addrFrom)
* period than if we divided the cycle count by 88, but a shorter LBA7 period is probably helpful in terms of
* overall performance.
*
* @param {number} nBit
* @param {number} iBit
* @return {number}
*/
ChipSet.prototype.getVT100LBA = function(nBit)
ChipSet.prototype.getVT100LBA = function(iBit)
{
return (this.cpu.getCycles() & (1 << (nBit - 1))) << 1;
return (this.cpu.getCycles() & (1 << (iBit - 1))) << 1;
};
/**

View file

@ -109,7 +109,7 @@ Keyboard.ASCII = {
Keyboard.KEYCODE = {
/* 0x08 */ BS: 8,
/* 0x09 */ TAB: 9,
/* 0x0A */ LF: 10,
/* 0x0A */ LF: 10, // TODO: Determine if any key actually generates this (I suspect there is none)
/* 0x0D */ CR: 13,
/* 0x10 */ SHIFT: 16,
/* 0x11 */ CTRL: 17,
@ -249,7 +249,7 @@ Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.FF_DASH] = Keyboard.ASCII['-'];
Keyboard.MINPRESSTIME = 100; // 100ms
/**
* Alternate keyCode mappings (to support the popular WASD directional mappings)
* Alternate keyCode mappings (to support popular "WASD"-style directional-key mappings)
*
* TODO: ES6 computed property name support may now be in all mainstream browsers, allowing us to use
* a simple object literal for this and all other object initializations.
@ -289,7 +289,8 @@ Keyboard.VT100 = {
* in the aKeysActive array, along with a final interrupt for KEYLAST.
*/
ADDRESS: {
PORT: 0x82
PORT: 0x82,
INIT: 0x7F
},
/*
* Writing port 0x82 updates the VT100's keyboard status byte via the keyboard's UART data input.
@ -376,7 +377,7 @@ Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.NUM_7] = 0x40;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.F4] = 0x41; // aka PF4
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.F2] = 0x42; // aka PF2
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.NUM_0] = 0x43;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.LF] = 0x44;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.F7] = 0x44; // aka LINE FEED
Keyboard.VT100.KEYMAP[Keyboard.ASCII['\\']] = 0x45;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['|']] = 0xC5;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.L] = 0x46;
@ -397,7 +398,7 @@ Keyboard.VT100.KEYMAP[Keyboard.ASCII.H] = 0x58;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.D] = 0x59;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.S] = 0x5A;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.NUM_DEL] = 0x60; // keypad period
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.F7] = 0x61; // aka KEYPAD COMMA
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.F5] = 0x61; // aka KEYPAD COMMA
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.NUM_5] = 0x62;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.NUM_4] = 0x63;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.CR] = 0x64; // TODO: Figure out why the Technical Manual lists CR at both 0x04 and 0x64
@ -412,7 +413,7 @@ Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.F8] = 0x6A; // aka NO SCROLL
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.NUM_9] = 0x70;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.NUM_3] = 0x71;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.NUM_6] = 0x72;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.NUM_SUB] = 0x73;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.NUM_SUB] = 0x73; // aka KEYPAD MINUS
Keyboard.VT100.KEYMAP[Keyboard.ASCII['/']] = 0x75;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['?']] = 0xF5;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.M] = 0x76;
@ -582,7 +583,8 @@ Keyboard.prototype.powerDown = function(fSave, fShutdown)
Keyboard.VT100.INIT = [
[
Keyboard.VT100.STATUS.INIT,
Keyboard.VT100.STATUS.INIT, // bVT100Status
Keyboard.VT100.ADDRESS.INIT, // bVT100Address
-1 // iKeyNext
]
];
@ -596,7 +598,7 @@ Keyboard.prototype.reset = function()
{
/*
* As keyDown events are encountered, a corresponding "softCode" is looked up. If one is found,
* then an entry for the key is added to the aKeysActive array. Each "key" entry contains:
* then an entry for the key is added to the aKeysActive array. Each "key" entry in aKeysActive contains:
*
* softCode: number or string representing the key pressed
* msDown: timestamp of the most recent "down" event
@ -626,7 +628,7 @@ Keyboard.prototype.save = function()
case Keyboard.SI1978.MODEL:
break;
case Keyboard.VT100.MODEL:
state.set(0, [this.bVT100Status]);
state.set(0, [this.bVT100Status, this.bVT100Address, -1]);
break;
}
return state.data();
@ -652,7 +654,8 @@ Keyboard.prototype.restore = function(data)
case Keyboard.VT100.MODEL:
this.bVT100Status = a[0];
this.updateLEDs(this.bVT100Status & Keyboard.VT100.STATUS.LEDS);
this.iKeyNext = a[1];
this.bVT100Address = a[1];
this.iKeyNext = a[2];
return true;
}
}
@ -739,7 +742,7 @@ Keyboard.prototype.onKeyDown = function(event, fDown)
}
if (!COMPILED && this.messageEnabled(Messages.KEYS)) {
this.printMessage("onKey" + (fDown? "Down" : "Up") + "(" + keyCode + "): " + (fPass? "true" : "false"), true);
this.printMessage("onKey" + (fDown? "Down" : "Up") + "(" + keyCode + "): softCode=" + softCode + ", pass=" + (fPass? "true" : "false"), true);
}
return fPass;
@ -888,6 +891,11 @@ Keyboard.prototype.checkBusy = function()
/**
* inVT100UARTAddress(port, addrFrom)
*
* We take our cue from iKeyNext. If it's -1 (default), we simply return the last value latched
* in bVT100Address. Otherwise, if iKeyNext is a valid index into aKeysActive, we look up the key
* in the VT100.KEYMAP, latch it, and increment iKeyNext, else we latch Keyboard.VT100.KEYLAST
* and set iKeyNext to -1 again.
*
* @this {Keyboard}
* @param {number} port (0x82)
* @param {number} [addrFrom] (not defined if the Debugger is trying to write the specified port)
@ -895,19 +903,24 @@ Keyboard.prototype.checkBusy = function()
*/
Keyboard.prototype.inVT100UARTAddress = function(port, addrFrom)
{
var b = 0;
if (this.iKeyNext >= 0 && this.iKeyNext < this.aKeysActive.length) {
var key = this.aKeysActive[this.iKeyNext++];
b = Keyboard.VT100.KEYMAP[key.softCode];
if (b & 0x80) {
/*
* TODO: This code is supposed to be accompanied by a SHIFT key; make sure that it is.
*/
b &= 0x7F;
var b = this.bVT100Address;
if (this.iKeyNext >= 0) {
if (this.iKeyNext < this.aKeysActive.length) {
var key = this.aKeysActive[this.iKeyNext++];
b = Keyboard.VT100.KEYMAP[key.softCode];
if (b & 0x80) {
/*
* TODO: This code is supposed to be accompanied by a SHIFT key; make sure that it is.
*/
b &= 0x7F;
}
} else {
this.iKeyNext = -1;
b = Keyboard.VT100.KEYLAST;
}
this.println("keymap: " + str.toHexByte(b));
this.bVT100Address = b;
this.cpu.requestINTR(1);
}
if (!b) b = Keyboard.VT100.KEYLAST;
this.printMessageIO(port, null, addrFrom, "KBDUART.ADDRESS", b);
return b;
};

View file

@ -320,7 +320,10 @@ Video.prototype.initBuffers = function()
* for each font and draw characters by drawing from the font canvas to the target canvas.
*/
if (this.cxCell > 1) {
this.initCellCache(this.nColsBuffer * this.nRowsBuffer);
/*
* We add an extra column per row to store the visible line length at the start of every row.
*/
this.initCellCache((this.nColsBuffer + 1) * this.nRowsBuffer);
} else {
this.imageBuffer = this.contextScreen.createImageData(cxBuffer, cyBuffer);
this.nPixelsPerCell = (16 / this.nBitsPerPixel)|0;
@ -959,11 +962,12 @@ Video.prototype.updateChar = function(idFont, col, row, data, context)
};
/**
* updateVT100()
* updateVT100(fForced)
*
* @this {Video}
* @param {boolean} [fForced]
*/
Video.prototype.updateVT100 = function()
Video.prototype.updateVT100 = function(fForced)
{
var addrNext = this.addrBuffer, fontNext = -1;
@ -979,6 +983,8 @@ Video.prototype.updateVT100 = function()
var nCols = 0;
var addr = addrNext;
var font = fontNext;
var nColsVisible = this.nColsBuffer;
if (font != Video.VT100.FONT.NORML) nColsVisible >>= 1;
while (true) {
var data = this.bus.getByteDirect(addr++);
if ((data & Video.VT100.LINETERM) == Video.VT100.LINETERM) {
@ -988,7 +994,7 @@ Video.prototype.updateVT100 = function()
addrNext += (b & Video.VT100.LINEATTR.ADDRBIAS)? Video.VT100.ADDRBIAS_LO : Video.VT100.ADDRBIAS_HI;
break;
}
if (nCols < this.abLineBuffer.length) {
if (nCols < nColsVisible) {
this.abLineBuffer[nCols++] = data;
} else {
break; // ideally, we would wait for a LINETERM byte, but it's not safe to loop without limit
@ -1011,13 +1017,21 @@ Video.prototype.updateVT100 = function()
}
/*
* Display the line buffer; ordinarily, the font number would always be valid after processing the "fill lines",
* but if the buffer isn't initialized yet, the usual LINETERM might be missing, so the font number might not be set.
* Display the line buffer; ordinarily, the font number would be valid after processing the "fill lines",
* but if the buffer isn't initialized yet, those lines might be missing, so the font number might not be set.
*/
if (font >= 0) {
/*
* Cell cache logic is complicated by the fact that a line may be single-width one frame and double-width
* the next. So we store the visible line length at the start of each row in the cache, which must match if
* the cache is considered valid for the current line.
*/
var fLineCacheValid = this.fCellCacheValid && (this.aCellCache[iCell] == nColsVisible);
this.aCellCache[iCell++] = nColsVisible;
for (var iCol = 0; iCol < nCols; iCol++) {
data = this.abLineBuffer[iCol];
if (!this.fCellCacheValid || data !== this.aCellCache[iCell]) {
if (!fLineCacheValid || data !== this.aCellCache[iCell]) {
this.aCellCache[iCell] = data;
this.updateChar(font, iCol, nRows, data, this.contextBuffer);
cUpdated++;
}
@ -1027,6 +1041,18 @@ Video.prototype.updateVT100 = function()
nRows++;
}
this.assert(font < 0 || iCell === this.nCellCache);
if (MAXDEBUG && !fForced) {
var nSeconds = Date.now() / 1000;
if ((nSeconds|0) != (this.nUpdateSeconds|0)) {
this.nUpdateNumber = 0;
}
this.nUpdateNumber++;
this.nUpdateSeconds = nSeconds;
this.printMessage("updateVT100(): update #" + this.nUpdateNumber + " at " +this.nUpdateSeconds + " corner=" + str.toHexByte(this.aCellCache[1]) + " cycles=" + this.nCyclesPrev + " delta=" + this.nCyclesDelta);
}
this.fCellCacheValid = true;
if (cUpdated && this.contextBuffer) {
@ -1056,9 +1082,10 @@ Video.prototype.updateScreen = function(n)
{
var fClean;
var fUpdate = true;
var fForced = true;
if (n >= 0) {
fForced = false;
if (this.rateInterrupt) {
/*
* TODO: Incorporate these hard-coded interrupt vector numbers into configuration blocks.
@ -1093,11 +1120,11 @@ Video.prototype.updateScreen = function(n)
}
}
if (DEBUG) {
if (DEBUG && !fForced) {
var nCycles = this.cpu.getCycles();
var nCyclesDelta = nCycles - this.nCyclesPrev;
this.nCyclesDelta = nCycles - this.nCyclesPrev;
this.nCyclesPrev = nCycles;
this.printMessage("updateScreen(" + n + "): clean=" + fClean + ", update=" + fUpdate + ", cycles=" + nCycles + ", delta=" + nCyclesDelta);
if (MAXDEBUG) this.printMessage("updateScreen(" + n + "): clean=" + fClean + ", update=" + fUpdate + ", cycles=" + this.nCyclesPrev + ", delta=" + this.nCyclesDelta);
}
if (!fUpdate) {
@ -1105,32 +1132,34 @@ Video.prototype.updateScreen = function(n)
}
if (this.cxCell > 1) {
this.updateScreenText();
this.updateScreenText(fForced);
} else {
this.updateScreenGraphics();
this.updateScreenGraphics(fForced);
}
};
/**
* updateScreenText()
* updateScreenText(fForced)
*
* @this {Video}
* @param {boolean} [fForced]
*/
Video.prototype.updateScreenText = function()
Video.prototype.updateScreenText = function(fForced)
{
switch(this.nFormat) {
case Video.FORMAT.VT100:
this.updateVT100();
this.updateVT100(fForced);
break;
}
};
/**
* updateScreenGraphics()
* updateScreenGraphics(fForced)
*
* @this {Video}
* @param {boolean} [fForced]
*/
Video.prototype.updateScreenGraphics = function()
Video.prototype.updateScreenGraphics = function(fForced)
{
var addr = this.addrBuffer;
var addrLimit = addr + this.sizeBuffer;