Added proper clocking of VT100 keyboard UART, allowing us to restore the proper CPU speed (2.7Mhz) and maintain the correct cursor blink rate

This commit is contained in:
Jeff Parsons 2016-08-22 09:14:34 -07:00
commit bca19fd8ed
39 changed files with 9641 additions and 35 deletions

View file

@ -172,25 +172,10 @@ ChipSet8080.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 (in theory) that the CPU is running at 2.76Mhz.
*
* Hence the CPU component in the VT100's machine.xml SHOULD be defined as:
* 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. 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.
*
@ -259,12 +244,12 @@ ChipSet8080.VT100 = {
*
* On p. 4-56, the DC011 Block Diagram shows 8 outputs labeled LBA0 through LBA7. From p. 4-61:
*
* Several of the LBAs are used as general purpose clocks in the VT100. LBA 3 and LBA 4 are used to generate
* Several of the LBAs are used as general purpose clocks in the VT100. LBA3 and LBA4 are used to generate
* timing for the keyboard. These signals satisfy the keyboard's requirement of two square-waves, one twice the
* frequency of the other, even though every 16th transition is delayed (the second stage of the horizontal
* counter divides by 17, not 16). LBA 7 is used by the nonvolatile RAM.
* counter divides by 17, not 16). LBA7 is used by the nonvolatile RAM.
*
* And on p. 4-62, timings are provided for the LBA0 through LBA7 when the VT100 is in 80-column mode; in particular:
* And on p. 4-62, timings are provided for the LBA0 through LBA7; in particular:
*
* LBA6: 16.82353us (when LBA6 is low, for a period of 33.64706us)
* LBA7: 31.77778us (when LBA7 is high, for a period of 63.55556us)
@ -958,10 +943,11 @@ ChipSet8080.prototype.doNVRCommand = function()
*/
ChipSet8080.prototype.inVT100Flags = function(port, addrFrom)
{
var b = this.bFlags;
/*
* The NVR_CLK bit is driven by LBA7 (ie, bit 7 from Line Buffer Address generation); see the DC011 discussion above.
*/
var b = this.bFlags;
b &= ~ChipSet8080.VT100.FLAGS.NVR_CLK;
if (this.getVT100LBA(7)) {
b |= ChipSet8080.VT100.FLAGS.NVR_CLK;
@ -969,18 +955,22 @@ ChipSet8080.prototype.inVT100Flags = function(port, addrFrom)
this.doNVRCommand();
}
}
b &= ~ChipSet8080.VT100.FLAGS.NVR_DATA;
if (this.bNVROut) {
b |= ChipSet8080.VT100.FLAGS.NVR_DATA;
}
b &= ~ChipSet8080.VT100.FLAGS.KBD_XMIT;
if (this.kbd && this.kbd.isTransmitterReady()) {
if (this.kbd && this.kbd.isVT100TransmitterReady()) {
b |= ChipSet8080.VT100.FLAGS.KBD_XMIT;
}
b &= ~ChipSet8080.VT100.FLAGS.UART_XMIT;
if (this.serial && this.serial.isTransmitterReady()) {
b |= ChipSet8080.VT100.FLAGS.UART_XMIT;
}
this.bFlags = b;
this.printMessageIO(port, null, addrFrom, "FLAGS", b);
return b;
@ -1045,7 +1035,7 @@ ChipSet8080.prototype.outVT100DC012 = function(port, b, addrFrom)
this.bDC012Blink = ~this.bDC012Blink;
break;
case 0x1:
// TODO: Clear vertical frequency interrupt
// TODO: Clear vertical frequency interrupt?
break;
case 0x2:
case 0x3:

View file

@ -580,9 +580,11 @@ Keyboard8080.prototype.powerDown = function(fSave, fShutdown)
Keyboard8080.VT100.INIT = [
[
Keyboard8080.VT100.STATUS.INIT, // bVT100Status
Keyboard8080.VT100.ADDRESS.INIT, // bVT100Address
-1 // iKeyNext
Keyboard8080.VT100.STATUS.INIT, // bVT100Status
Keyboard8080.VT100.ADDRESS.INIT, // bVT100Address
false, // fVT100UARTBusy
0, // nVT100UARTCycleSnap
-1 // iKeyNext
]
];
@ -625,7 +627,7 @@ Keyboard8080.prototype.save = function()
case Keyboard8080.SI1978.MODEL:
break;
case Keyboard8080.VT100.MODEL:
state.set(0, [this.bVT100Status, this.bVT100Address, -1]);
state.set(0, [this.bVT100Status, this.bVT100Address, this.fVT100UARTBusy, this.nVT100UARTCycleSnap, -1]);
break;
}
return state.data();
@ -652,7 +654,9 @@ Keyboard8080.prototype.restore = function(data)
this.bVT100Status = a[0];
this.updateLEDs(this.bVT100Status & Keyboard8080.VT100.STATUS.LEDS);
this.bVT100Address = a[1];
this.iKeyNext = a[2];
this.fVT100UARTBusy = a[2];
this.nVT100UARTCycleSnap = a[3];
this.iKeyNext = a[4];
return true;
}
}
@ -871,17 +875,48 @@ Keyboard8080.prototype.checkSoftKeysToRelease = function()
};
/**
* isTransmitterReady()
* isVT100TransmitterReady()
*
* 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).
* Called whenever the VT100 ChipSet circuit needs the Keyboard UART's transmitter status.
*
* From p. 4-32 of the VT100 Technical Manual (July 1982):
*
* The operating clock for the keyboard interface comes from an address line in the video processor (LBA4).
* This signal has an average period of 7.945 microseconds. Each data byte is transmitted with one start bit
* and one stop bit, and each bit lasts 16 clock periods. The total time for each data byte is 160 times 7.945
* or 1.27 milliseconds. Each time the Transmit Buffer Empty flag on the terminal's UART gets set (when the
* current byte is being transmitted), the microprocessor loads another byte into the transmit buffer. In this
* way, the stream of status bytes to the keyboard is continuous.
*
* We used to always return true (after all, what's wrong with an infinitely fast UART?), but unfortunately,
* the VT100 firmware relies on the UART's slow transmission speed to drive cursor blink rate. We have several
* options:
*
* 1) Snapshot the CPU cycle count each time a byte is transmitted (see outVT100UARTStatus()) and then every
* time this is polled, see if the cycle count has exceeded the snapshot value by the necessary threshold
* amount; if we assume 361.69ns per CPU cycle, there are 22 CPU cycles for every 1 LBA4 cycle, and since
* transmission time is supposed to last for 160 LBA4 cycles, that means 22*160 CPU cycles, or 3520 cycles.
*
* 2) Set a CPU timer using the new setTimer() interface, which can be passed the number of milliseconds to
* wait before firing (in this case, 7945ms).
*
* 3) Call the ChipSet's getVT100LBA(4) function for the state of the simulated LBA4, and count 160 LBA4
* transitions; however, that would be the worst solution, because there's no guarantee that the firmware's
* UART polling will occur regularly and/or frequently enough for us to catch every LBA4 transition.
*
* I'm going with solution #1 because it's less overhead.
*
* @this {Keyboard8080}
* @return {boolean} (true if ready, false if not)
*/
Keyboard8080.prototype.isTransmitterReady = function()
Keyboard8080.prototype.isVT100TransmitterReady = function()
{
return true;
if (this.fVT100UARTBusy) {
if (this.cpu.getCycles() >= this.nVT100UARTCycleSnap + 3520) {
this.fVT100UARTBusy = false;
}
}
return !this.fVT100UARTBusy;
};
/**
@ -944,6 +979,8 @@ Keyboard8080.prototype.outVT100UARTStatus = function(port, b, addrFrom)
{
this.printMessageIO(port, b, addrFrom, "KBDUART.STATUS");
this.bVT100Status = b;
this.fVT100UARTBusy = true;
this.nVT100UARTCycleSnap = this.cpu.getCycles();
this.updateLEDs(b & Keyboard8080.VT100.STATUS.LEDS);
if (b & Keyboard8080.VT100.STATUS.START) {
this.iKeyNext = 0;

View file

@ -823,7 +823,7 @@ SerialPort8080.prototype.outData = function(port, bOut, addrFrom)
this.bStatus &= ~(SerialPort8080.UART8251.STATUS.XMIT_READY | SerialPort8080.UART8251.STATUS.XMIT_EMPTY);
/*
* If we're transmitting to a virtual device that has no measurable delay, this code may clear XMIT_READY
* too quickly.
* too quickly:
*
* if (this.transmitByte(bOut)) {
* this.bStatus |= (SerialPort8080.UART8251.STATUS.XMIT_READY | SerialPort8080.UART8251.STATUS.XMIT_EMPTY);