Enabled smooth scrolling, connected SerialPort UART transmitter status to the ChipSet FLAGS buffer (matching how the Keyboard UART transmitter status is connected), enabled binding of terminal I/O to the Control Panel output window (if any), and added simulation of received data at the programmed baud rate (but a better solution has been noted and needs to be implemented)

This commit is contained in:
Jeff Parsons 2016-08-16 09:34:41 -07:00
commit cf24023d5d
8 changed files with 609 additions and 477 deletions

View file

@ -220,16 +220,16 @@ ChipSet.SI1978 = {
*/
ChipSet.VT100 = {
MODEL: 100.0,
FLAGS_BUFFER: {
FLAGS: {
PORT: 0x42, // read-only
XMIT: 0x01, // PUSART transmit buffer empty if SET
UART_XMIT: 0x01, // PUSART transmit buffer empty if SET
NO_AVO: 0x02, // AVO present if CLEAR
NO_GFX: 0x04, // VT125 graphics board present if CLEAR
OPTION: 0x08, // OPTION present if SET
NO_EVEN: 0x10, // EVEN FIELD active if CLEAR
NVR_DATA: 0x20, // NVR DATA if SET
NVR_CLK: 0x40, // NVR CLOCK if SET
KBD_XMIT: 0x80 // KBD XMIT BUFFER empty if SET
KBD_XMIT: 0x80 // KBD transmit buffer empty if SET
},
BRIGHTNESS: {
PORT: 0x42, // write-only
@ -424,6 +424,7 @@ ChipSet.prototype.initBus = function(cmp, bus, cpu, dbg)
this.dbg = dbg;
this.cmp = cmp;
this.kbd = /** @type {Keyboard} */ (cmp.getMachineComponent("Keyboard"));
this.serial = /** @type {SerialPort} */ (cmp.getMachineComponent("SerialPort"));
this.video = /** @type {Video} */ (cmp.getMachineComponent("Video"));
bus.addPortInputTable(this, this.config.portsInput);
bus.addPortOutputTable(this, this.config.portsOutput);
@ -474,7 +475,7 @@ ChipSet.SI1978.INIT = [
ChipSet.VT100.INIT = [
[
ChipSet.VT100.BRIGHTNESS.INIT,
ChipSet.VT100.FLAGS_BUFFER.NO_AVO | ChipSet.VT100.FLAGS_BUFFER.NO_GFX
ChipSet.VT100.FLAGS.NO_AVO | ChipSet.VT100.FLAGS.NO_GFX
],
[
ChipSet.VT100.DC011.INITCOLS,
@ -531,7 +532,7 @@ ChipSet.prototype.save = function()
state.set(0, [this.bStatus0, this.bStatus1, this.bStatus2, this.wShiftData, this.bShiftCount, this.bSound1, this.bSound2]);
break;
case ChipSet.VT100.MODEL:
state.set(0, [this.bBrightness, this.bFlagsBuffer]);
state.set(0, [this.bBrightness, this.bFlags]);
state.set(1, [this.bDC011Cols, this.bDC011Rate]);
state.set(2, [this.bDC012Scroll, this.bDC012Blink, this.bDC012Reverse, this.bDC012Attr]);
state.set(3, [this.dNVRAddr, this.wNVRData, this.bNVRLatch, this.bNVROut, this.aNVRWords]);
@ -555,31 +556,31 @@ ChipSet.prototype.restore = function(data)
if (data && (a = data[0]) && a.length) {
switch(this.config.MODEL) {
case ChipSet.SI1978.MODEL:
this.bStatus0 = a[0];
this.bStatus1 = a[1];
this.bStatus2 = a[2];
this.wShiftData = a[3];
this.bShiftCount = a[4];
this.bSound1 = a[5];
this.bSound2 = a[6];
this.bStatus0 = a[0];
this.bStatus1 = a[1];
this.bStatus2 = a[2];
this.wShiftData = a[3];
this.bShiftCount = a[4];
this.bSound1 = a[5];
this.bSound2 = a[6];
return true;
case ChipSet.VT100.MODEL:
this.bBrightness = a[0];
this.bFlagsBuffer = a[1];
this.bBrightness = a[0];
this.bFlags = a[1];
a = data[1];
this.bDC011Cols = a[0];
this.bDC011Rate = a[1];
this.bDC011Cols = a[0];
this.bDC011Rate = a[1];
a = data[2];
this.bDC012Scroll = a[0];
this.bDC012Blink = a[1];
this.bDC012Scroll = a[0];
this.bDC012Blink = a[1];
this.bDC012Reverse = a[2];
this.bDC012Attr = a[3];
this.bDC012Attr = a[3];
a = data[3];
this.dNVRAddr = a[0]; // 20-bit address
this.wNVRData = a[1]; // 14-bit word
this.bNVRLatch = a[2]; // 1 byte
this.bNVROut = a[3]; // 1 bit
this.aNVRWords = a[4]; // 100 14-bit words
this.dNVRAddr = a[0]; // 20-bit address
this.wNVRData = a[1]; // 14-bit word
this.bNVRLatch = a[2]; // 1 byte
this.bNVROut = a[3]; // 1 bit
this.aNVRWords = a[4]; // 100 14-bit words
return true;
}
}
@ -883,36 +884,40 @@ ChipSet.prototype.doNVRCommand = function()
};
/**
* inVT100FlagsBuffer(port, addrFrom)
* inVT100Flags(port, addrFrom)
*
* @this {ChipSet}
* @param {number} port (0x42)
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
* @return {number} simulated port value
*/
ChipSet.prototype.inVT100FlagsBuffer = function(port, addrFrom)
ChipSet.prototype.inVT100Flags = function(port, addrFrom)
{
/*
* The NVR_CLK bit is driven by LBA7 (ie, bit 7 from Line Buffer Address generation); see the DC011 discussion above.
*/
var b = this.bFlagsBuffer;
b &= ~ChipSet.VT100.FLAGS_BUFFER.NVR_CLK;
var b = this.bFlags;
b &= ~ChipSet.VT100.FLAGS.NVR_CLK;
if (this.getVT100LBA(7)) {
b |= ChipSet.VT100.FLAGS_BUFFER.NVR_CLK;
if (b != this.bFlagsBuffer) {
b |= ChipSet.VT100.FLAGS.NVR_CLK;
if (b != this.bFlags) {
this.doNVRCommand();
}
}
b &= ~ChipSet.VT100.FLAGS_BUFFER.NVR_DATA;
b &= ~ChipSet.VT100.FLAGS.NVR_DATA;
if (this.bNVROut) {
b |= ChipSet.VT100.FLAGS_BUFFER.NVR_DATA;
b |= ChipSet.VT100.FLAGS.NVR_DATA;
}
b &= ~ChipSet.VT100.FLAGS_BUFFER.KBD_XMIT;
if (this.kbd && !this.kbd.checkBusy()) {
b |= ChipSet.VT100.FLAGS_BUFFER.KBD_XMIT;
b &= ~ChipSet.VT100.FLAGS.KBD_XMIT;
if (this.kbd && this.kbd.isTransmitterReady()) {
b |= ChipSet.VT100.FLAGS.KBD_XMIT;
}
this.bFlagsBuffer = b;
this.printMessageIO(port, null, addrFrom, "FLAGS.BUFFER", b);
b &= ~ChipSet.VT100.FLAGS.UART_XMIT;
if (this.serial && this.serial.isTransmitterReady()) {
b |= ChipSet.VT100.FLAGS.UART_XMIT;
}
this.bFlags = b;
this.printMessageIO(port, null, addrFrom, "FLAGS", b);
return b;
};
@ -967,6 +972,7 @@ ChipSet.prototype.outVT100DC012 = function(port, b, addrFrom)
break;
case 0x1:
this.bDC012Scroll = (this.bDC012Scroll & ~0xC) | (bOpt << 2);
if (this.video) this.video.updateScrollOffset(this.bDC012Scroll);
break;
case 0x2:
switch(bOpt) {
@ -1013,7 +1019,7 @@ ChipSet.prototype.outVT100DC011 = function(port, b, addrFrom)
this.bDC011Cols = b;
if (this.video) {
var nCols = (this.bDC011Cols == ChipSet.VT100.DC011.COLS132? 132 : 80);
var nRows = (nCols > 80 && (this.bFlagsBuffer & ChipSet.VT100.FLAGS_BUFFER.NO_AVO)? 14 : 24);
var nRows = (nCols > 80 && (this.bFlags & ChipSet.VT100.FLAGS.NO_AVO)? 14 : 24);
this.video.updateDimensions(nCols, nRows);
}
}
@ -1039,7 +1045,7 @@ ChipSet.SI1978.portsOutput = {
};
ChipSet.VT100.portsInput = {
0x42: ChipSet.prototype.inVT100FlagsBuffer
0x42: ChipSet.prototype.inVT100Flags
};
ChipSet.VT100.portsOutput = {

View file

@ -870,17 +870,17 @@ Keyboard.prototype.checkSoftKeysToRelease = function()
};
/**
* checkBusy()
* isTransmitterReady()
*
* Called whenever a ChipSet circuit needs the keyboard's UART status.
* Currently, we have no busy conditions (our virtual keyboard is infinitely fast).
* Called whenever a ChipSet circuit needs the Keyboard UART's transmitter status.
* Currently, we have no busy conditions (our virtual keyboard transmitter is infinitely fast).
*
* @this {Keyboard}
* @return {boolean}
* @return {boolean} (true if ready, false if not)
*/
Keyboard.prototype.checkBusy = function()
Keyboard.prototype.isTransmitterReady = function()
{
return false;
return true;
};
/**

View file

@ -36,7 +36,6 @@ if (NODE) {
var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var Messages = require("./messages");
var ChipSet = require("./chipset");
var State = require("./state");
}
@ -115,6 +114,15 @@ function SerialPort(parmsSerial) {
*/
Component.bindExternalControl(this, sBinding, SerialPort.sIOBuffer);
}
/*
* Define a setTimeout() function that receiveData() can use when there's more data to receive.
*/
this.fnCheckDataReceived = function(serial) {
return function() {
serial.receiveData();
}
}(this);
}
/*
@ -146,7 +154,7 @@ SerialPort.UART8251 = {
PARITY_ENABLE: 0x10,
EVEN_PARITY: 0x20,
STOP_BITS: 0xC0, // 00=invalid, 01=1, 10=1.5, 11=2
INIT: 0x8E // 16x baudrate, 8 data bits, no parity, 1.5 stop bits
INIT: 0x8E // 16x baud rate, 8 data bits, no parity, 1.5 stop bits
},
/*
* Format of COMMAND byte written to CONTROL port 0x1
@ -177,9 +185,10 @@ SerialPort.UART8251 = {
INIT: 0x85 // XMIT_READY | XMIT_EMPTY | DSR
},
/*
* Format of BAUDRATE byte written to port 0x2
* Format of BAUDRATES byte written to port 0x2
*
* Each nibble is an index (0x0-0xF) into a set internal CPU clock divisors that yield the following baud rates:
* Each nibble is an index (0x0-0xF) into a set of internal CPU clock divisors that yield the
* following baud rates:
*
* Index Divisor Baud Rate
* ----- ------- ---------
@ -199,15 +208,15 @@ SerialPort.UART8251 = {
* 0xD 36 4800
* 0xE 18 9600 (default)
* 0xF 9 19200
*
* TODO: I'm not really sure at this point which nibble is for the XMIT rate and which is for the RECV
* rate; I just made a random guess.
*/
BAUDRATE: {
XMIT_RATE: 0x0F,
RECV_RATE: 0xF0,
BAUDRATES: {
RECV_RATE: 0x0F,
XMIT_RATE: 0xF0,
INIT: 0xEE // default to 9600 (0xE) for both XMIT and RECV
}
},
BAUDTABLE: [
50, 75, 110, 134.5, 150, 200, 300, 600, 1200, 1800, 2000, 2400, 3600, 4800, 9600, 19200
]
};
SerialPort.UART8251.INIT = [
@ -217,7 +226,7 @@ SerialPort.UART8251.INIT = [
SerialPort.UART8251.STATUS.INIT,
SerialPort.UART8251.MODE.INIT,
SerialPort.UART8251.COMMAND.INIT,
SerialPort.UART8251.BAUDRATE.INIT
SerialPort.UART8251.BAUDRATES.INIT
];
/*
@ -272,7 +281,7 @@ SerialPort.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
if (keyCode === 0x08 || event.ctrlKey && keyCode >= 0x41 && keyCode <= 0x5A) {
if (event.preventDefault) event.preventDefault();
if (keyCode > 0x40) keyCode -= 0x40;
serial.sendByteIn(keyCode);
serial.receiveByte(keyCode);
}
return true;
};
@ -284,7 +293,7 @@ SerialPort.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
*/
event = event || window.event;
var keyCode = event.which || event.keyCode;
// serial.sendRBR([keyCode]);
serial.receiveByte(keyCode);
/*
* Since we're going to remove the "readonly" attribute from the <textarea> control
* (so that the soft keyboard activates on iOS), instead of calling preventDefault() for
@ -317,8 +326,8 @@ SerialPort.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
*/
sValue = sValue.replace(/\\n/g, "\n").replace(/\\r/g, "\r");
control.onclick = function onClickTest(event) {
serial.sDataIn = sValue;
serial.sendDataIn();
serial.sDataReceived = sValue;
serial.receiveData();
/*
* Give focus back to the machine (since clicking the button takes focus away).
*/
@ -444,7 +453,7 @@ SerialPort.prototype.initState = function(data)
this.bStatus = data[i++];
this.bMode = data[i++];
this.bCommand = data[i++];
this.bBaudRate = data[i];
this.bBaudRates = data[i];
return true;
};
@ -464,18 +473,37 @@ SerialPort.prototype.saveRegisters = function()
data[i++] = this.bStatus;
data[i++] = this.bMode;
data[i++] = this.bCommand;
data[i] = this.bBaudRate;
data[i] = this.bBaudRates;
return data;
};
/**
* sendByteIn(b)
* getBaudTimeout(maskRate)
*
* @this {SerialPort}
* @param {number} maskRate (either SerialPort.UART8251.BAUDRATES.RECV_RATE or SerialPort.UART8251.BAUDRATES.XMIT_RATE)
* @return {number} (number of milliseconds per byte)
*/
SerialPort.prototype.getBaudTimeout = function(maskRate)
{
var indexRate = (this.bBaudRates & maskRate);
if (!(maskRate & 0xf)) indexRate >>= 4;
var nBaud = SerialPort.UART8251.BAUDTABLE[indexRate];
var nBits = ((this.bMode & SerialPort.UART8251.MODE.DATA_BITS) >> 2) + 6; // includes an extra +1 for start bit
if (this.bMode & SerialPort.UART8251.MODE.PARITY_ENABLE) nBits++;
nBits += ((((this.bMode & SerialPort.UART8251.MODE.STOP_BITS) >> 6) + 1) >> 1);
var nBytesPerSecond = Math.round(nBaud / nBits);
return 1000 / nBytesPerSecond;
};
/**
* receiveByte(b)
*
* @this {SerialPort}
* @param {number} b
* @return {boolean}
*/
SerialPort.prototype.sendByteIn = function(b)
SerialPort.prototype.receiveByte = function(b)
{
if (!(this.bStatus & SerialPort.UART8251.STATUS.RECV_FULL)) {
this.bDataIn = b;
@ -487,15 +515,31 @@ SerialPort.prototype.sendByteIn = function(b)
};
/**
* sendDataIn()
* receiveData()
*
* @this {SerialPort}
*/
SerialPort.prototype.sendDataIn = function()
SerialPort.prototype.receiveData = function()
{
if (this.sDataIn) {
if (this.sendByteIn(this.sDataIn.charCodeAt(0))) {
this.sDataIn = this.sDataIn.substr(1);
if (this.sDataReceived) {
if (this.receiveByte(this.sDataReceived.charCodeAt(0))) {
this.sDataReceived = this.sDataReceived.substr(1);
}
/*
* TODO: If data has become undeliverable for some reason (eg, the Debugger has paused execution),
* we should stop setting timeouts, and add one or more notification mechanisms to kickstart it again.
*/
if (this.sDataReceived) {
/*
* TODO: setTimeout() is a less-than-ideal solution, because it's too slow; timeouts won't fire until
* the end of a CPU burst. So instead of calculating a number of milliseconds, we should calculate a
* number of CPU cycles, and create a CPU notification mechanism that calls us back after that many cycles
* have elapsed (and which will automatically shorten the current CPU burst as needed).
*
* This will also solve the other issue noted above, because if the CPU has been halted, it won't be
* generating any notifications either.
*/
setTimeout(this.fnCheckDataReceived, this.getBaudTimeout(SerialPort.UART8251.BAUDRATES.RECV_RATE));
}
}
};
@ -548,6 +592,19 @@ SerialPort.prototype.echoByte = function(b)
return false;
};
/**
* isTransmitterReady()
*
* Called whenever a ChipSet circuit needs the SerialPort UART's transmitter status.
*
* @this {SerialPort}
* @return {boolean} (true if ready, false if not)
*/
SerialPort.prototype.isTransmitterReady = function()
{
return !!(this.bStatus & SerialPort.UART8251.STATUS.XMIT_READY);
};
/**
* inData(port, addrFrom)
*
@ -561,7 +618,6 @@ SerialPort.prototype.inData = function(port, addrFrom)
var b = this.bDataIn;
this.printMessageIO(port, null, addrFrom, "DATA", b);
this.bStatus &= ~SerialPort.UART8251.STATUS.RECV_FULL;
this.sendDataIn(); // if there is still queued incoming data, send another byte
return b;
};
@ -592,6 +648,10 @@ SerialPort.prototype.outData = function(port, bOut, addrFrom)
{
this.printMessageIO(port, bOut, addrFrom, "DATA");
this.bDataOut = bOut;
this.bStatus &= ~(SerialPort.UART8251.STATUS.XMIT_READY | SerialPort.UART8251.STATUS.XMIT_EMPTY);
if (this.echoByte(bOut)) {
this.bStatus |= (SerialPort.UART8251.STATUS.XMIT_READY | SerialPort.UART8251.STATUS.XMIT_EMPTY);
}
};
/**
@ -622,17 +682,17 @@ SerialPort.prototype.outControl = function(port, bOut, addrFrom)
};
/**
* outBaudRate(port, bOut, addrFrom)
* outBaudRates(port, bOut, addrFrom)
*
* @this {SerialPort}
* @param {number} port (0x2)
* @param {number} bOut
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
*/
SerialPort.prototype.outBaudRate = function(port, bOut, addrFrom)
SerialPort.prototype.outBaudRates = function(port, bOut, addrFrom)
{
this.printMessageIO(port, bOut, addrFrom, "BAUDRATE");
this.bBaudRate = bOut;
this.printMessageIO(port, bOut, addrFrom, "BAUDRATES");
this.bBaudRates = bOut;
};
/*
@ -650,7 +710,7 @@ SerialPort.aPortInput = {
SerialPort.aPortOutput = {
0x0: SerialPort.prototype.outData,
0x1: SerialPort.prototype.outControl,
0x2: SerialPort.prototype.outBaudRate
0x2: SerialPort.prototype.outBaudRates
};
/**

View file

@ -154,7 +154,10 @@ function Video(parmsVideo, canvas, context, textarea, container)
* Now that we've finished using nRowsBuffer to help define the screen size, we add one more
* row for text modes, to account for the VT100's scroll line buffer (used for smooth scrolling).
*/
if (this.cyCell > 1) this.nRowsBuffer++;
if (this.cyCell > 1) {
this.nRowsBuffer++;
this.bScrollOffset = 0;
}
/*
* Support for disabling (or, less commonly, enabling) image smoothing, which all browsers
@ -762,6 +765,23 @@ Video.prototype.updateRate = function(nRate)
this.rateMonitor = nRate;
};
/**
* updateScrollOffset(bScroll)
*
* Called from the ChipSet component whenever the screen scroll offset has been dynamically altered.
*
* @this {Video}
* @param {number} bScroll
*/
Video.prototype.updateScrollOffset = function(bScroll)
{
this.printMessage("updateScrollOffset(" + bScroll + ")");
if (this.bScrollOffset !== bScroll) {
this.bScrollOffset = bScroll;
this.updateScreen(-1);
}
};
/**
* doFullScreen()
*
@ -1091,6 +1111,8 @@ Video.prototype.updateVT100 = function(fForced)
nRows++;
}
this.fCellCacheValid = true;
this.assert(font < 0 || iCell === this.nCellCache);
if (MAXDEBUG && !fForced) {
@ -1103,14 +1125,23 @@ Video.prototype.updateVT100 = function(fForced)
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) {
if ((cUpdated || fForced) && this.contextBuffer) {
/*
* NOTE: We must subtract cyCell from cyBuffer to avoid displaying the extra "scroll line" that we normally
* buffer to support smooth-scrolling.
* We must subtract cyCell from cyBuffer to avoid displaying the extra "scroll line" that we normally
* buffer, in support of smooth scrolling. Speaking of which, we must also add bScrollOffset to ySrc
* (well, ySrc is always relative to zero, so no add is actually required).
*/
this.contextScreen.drawImage(this.canvasBuffer, 0, 0, this.cxBuffer, this.cyBuffer - this.cyCell, this.xScreenOffset, this.yScreenOffset, this.cxScreenOffset, this.cyScreenOffset);
this.contextScreen.drawImage(
this.canvasBuffer,
0, // xSrc
this.bScrollOffset, // ySrc
this.cxBuffer, // cxSrc
this.cyBuffer - this.cyCell, // cySrc
this.xScreenOffset, // xDst
this.yScreenOffset, // yDst
this.cxScreenOffset, // cxDst
this.cyScreenOffset // cyDst
);
}
};