Merge branch 'next-release'

This commit is contained in:
Jeff Parsons 2016-08-15 15:00:48 -07:00
commit c349a56218
13 changed files with 962 additions and 559 deletions

View file

@ -15,12 +15,41 @@ This is a PCjs work-in-progress emulation of another 8080-based machine: the VT1
Unlike other VT100 emulators, it is not simply an emulation of VT100 protocols. It is a simulation of the original VT100
machine, running the [VT100 Firmware](/devices/pc8080/rom/vt100/) inside the [PC8080](/modules/pc8080/) CPU emulator.
A [Debugger Configuration](/devices/pc8080/machine/vt100/debugger/) is also available.
Admittedly, terminals aren't that useful in isolation, since they're designed to be connected to other (host) machines.
But once this PCjs VT100 Terminal simulation is fully operational, you can expect to see it used in conjunction with a variety
of other PCjs machines.
For now, play with the [Debugger Configuration](/devices/pc8080/machine/vt100/debugger/), which also provides information
about VT100 internals and links to other technical resources.
{% include machine.html id="vt100" %}
VT100 Keys
----------
Function keys are mapped as follows:
- F1: PF1
- F2: PF2
- F3: PF3
- F4: PF4
- F6: BREAK
- F7: LINE FEED
- F8: NO SCROLL
- F9: SET-UP
From the SET-UP screen, you can press **4** to switch to LOCAL mode and verify local operation of most VT100
keys. The following keys have special meaning inside SET-UP Mode.
### SET-UP Mode Keys
- 0: RESET
- 2: SET/CLEAR TAB
- 3: CLEAR ALL TABS
- 4: ONLINE/LOCAL
- 5: SET-UP A/B
- 6: TOGGLE FEATURE
- 7: TRANSMIT SPEED
- 8: RECEIVE SPEED
- 9: 80/132 COLUMNS
- SHIFT-S: Save SET-UP Features
- SHIFT-R: Restore SET-UP Features

View file

@ -12,10 +12,17 @@ DEC VT100 Terminal with Debugger
--------------------------------
The [PC8080](/modules/pc8080/) machine below is configured to simulate a [VT100 Terminal](/devices/pc8080/machine/vt100/)
with a Control Panel and Debugger. It is running the original [VT100 Firmware](/devices/pc8080/rom/vt100/) inside the
[PC8080](/modules/pc8080/) CPU emulator. You'll also find assorted [Hardware Notes](#vt100-memory-usage) below.
with a Control Panel and Debugger. When you press the "Run" button, it will start running the original
[VT100 Firmware](/devices/pc8080/rom/vt100/) inside the [PC8080](/modules/pc8080/) CPU emulator.
Click the "Run" button to start the simulation. The VT100 KEYMAP table in [keyboard.js](/modules/pc8080/lib/keyboard.js)
Information about [VT100 Keys](#vt100-keys) along with assorted [Hardware Notes](#vt100-memory-usage) are provided below.
{% include machine.html id="vt100" %}
VT100 Keys
----------
The VT100 KEYMAP table in [keyboard.js](/modules/pc8080/lib/keyboard.js)
maps modern keys to VT100 key addresses, and most of the mappings are 1-1. Function keys are mapped as follows:
- F1: PF1
@ -27,10 +34,22 @@ maps modern keys to VT100 key addresses, and most of the mappings are 1-1. Func
- F8: NO SCROLL
- F9: SET-UP
The simulation is not fully operational yet, but it is now possible to access the SET-UP screen, press 4 to switch to LOCAL
mode, and verify that most VT100 keys work properly.
From the SET-UP screen, you can press **4** to switch to LOCAL mode and verify local operation of most VT100
keys. The following keys have special meaning inside SET-UP Mode.
{% include machine.html id="vt100" %}
### SET-UP Mode Keys
- 0: RESET
- 2: SET/CLEAR TAB
- 3: CLEAR ALL TABS
- 4: ONLINE/LOCAL
- 5: SET-UP A/B
- 6: TOGGLE FEATURE
- 7: TRANSMIT SPEED
- 8: RECEIVE SPEED
- 9: 80/132 COLUMNS
- SHIFT-S: Save SET-UP Features
- SHIFT-R: Restore SET-UP Features
VT100 Memory Usage
------------------
@ -227,6 +246,37 @@ Some additional observations:
set in the keyboard STATUS port (0x82), generating a "bell" (beep). The NVR checksum test used to fail when we initialized all
NVR words with the freshly-erased value of 0x3fff; now we initialize them with canned values (see ChipSet.VT100.INIT).
### SET-UP Mode
From "PART 2: SET-UP MODE", p. 2-6, of the VT100 Technical Manual (July 1982):
> Unlike most terminals, the VT100 does not use switches or jumpers to individually turn the built-in terminal features on
or off. Instead, the VT100 uses a nonvolatile memory (NVR) that always remembers what features have been selected, as if a
swiitch had been set.
> Selection and storage of built-in terminal features is performed in a special mode of operation called SET-UP mode.
When you enter SET-UP mode, the status of features stored in temporary memory shows on the screen. You can then change the
features and store any new feature selections either temporarily, by leaving SET-UP mode; or on a fixed basis, by performing
a Save operation. In either case, terminal operation reflects the new feature selection. If a recall operation is performed,
or the terminal is reset, or terminal power is turned off, all temporary feature settings are replaced by features that have
been stored on a fixed basis.
> ### SET-UP Features
> SET-UP mode provides two brief summaries of the current feature status. The first presentation - SET-UP A - displays
the location of tab stops set and a visual ruler that numbers each character position on the line. The second presentation -
SET-UP B - summarizes the status of the other terminal features.
> **SET-UP A** - To enter SET-UP A, press the **SET-UP** key. The display has a presentation similar to Figure 2-3. The bottom
line of the display consists of a "ruler" that numbers each character position available on a line. Each tab stop is shown by
a "T" above the ruler. If the tab stop(s) set are those desired, you may exit **SET-UP** mode by pressing the SET-UP key again
or you may now change the tabs to meet your requirements.
> **SET-UP B** - SET-UP B mode may only be entered from SET-UP A mode. To enter SET-UP B, from SET-UP A press the **5** key on
the main keyboard. The display looks like Figure 2-4. Figure 2-5 summarizes the SET-UP B presentation. This summary allows you
to quickly determine what features are enabled. For additional information on a feature refer to in Part 3, SET-UP Feature
Definitions.
Additional VT100 Resources
--------------------------

View file

@ -24,8 +24,12 @@
</menu>
</video>
<chipset id="chipset" model="VT100"/>
<keyboard id="keyboard" model="VT100"/>
<serial id="serial0" adapter="0"/>
<keyboard id="keyboard" model="VT100">
<control type="button" binding="setup" padleft="8px">SET-UP</control>
</keyboard>
<serial id="serial0" adapter="0">
<control type="button" binding="test" value="HELLO WORLD!\r\n" padleft="8px">TEST RECEIVER</control>
</serial>
<panel ref="/devices/pc8080/panel/wide.xml"/>
<debugger id="debugger" messages="mem|port" commands="s 8086"/>
</machine>

View file

@ -24,5 +24,10 @@
</menu>
</video>
<chipset id="chipset" model="VT100"/>
<keyboard id="keyboard" model="VT100"/>
<keyboard id="keyboard" model="VT100">
<control type="button" binding="setup" padleft="8px" padbottom="8px">SET-UP</control>
</keyboard>
<serial id="serial0" adapter="0">
<control type="button" binding="test" value="HELLO WORLD!\r\n" padleft="8px" padbottom="8px">TEST RECEIVER</control>
</serial>
</machine>

View file

@ -235,24 +235,52 @@ ChipSet.VT100 = {
PORT: 0x42, // write-only
INIT: 0x00 // for lack of a better guess
},
NVR: {
LATCH: {
PORT: 0x62 // write-only
},
CMD: {
ACCEPT_DATA: 0x0,
ACCEPT_ADDR: 0x1,
SHIFT_OUT: 0x2,
WRITE: 0x4,
ERASE: 0x5,
READ: 0x6,
STANDBY: 0x7
},
WORDMASK: 0x3fff // NVR words are 14-bit
/*
* The Technical Manual, p. 4-18, also notes that "Early VT100s can disable the receiver interrupt by
* programming D4 in the NVR latch. However, this is never used by the VT100."
* DC011 is referred to as a Timing Chip.
*
* As p. 4-55 (105) of the VT100 Technical Manual (July 1982) explains:
*
* The DCO11 is a custom designed bipolar circuit that provides most of the timing signals required by the
* video processor. Internal counters divide the output of a 24.0734 MHz oscillator (located elsewhere on the
* terminal controller module) into the lower frequencies that define dot, character, scan, and frame timing.
* The counters are programmable through various input pins to control the number of characters per line,
* the frequency at which the screen is refreshed, and whether the display is interlaced or noninterlaced.
* These parameters can be controlled through SET-UP mode or by the host.
*
* Table 4-6-1: Video Mode Selection (Write Address 0xC2)
*
* D5 D4 Configuration
* -- -- -------------
* 0 0 80-column mode, interlaced
* 0 1 132-column mode, interlaced
* 1 0 60Hz, non-interlaced
* 1 1 50Hz, non-interlaced
*
* 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
* 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.
*
* And on p. 4-62, timings are provided for the LBA0 through LBA7 when the VT100 is in 80-column mode; 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)
*
* If we assume that the CPU cycle count increments once every 361.69ns, it will increment roughly 88 times every
* time LBA7 toggles. So we can divide the CPU cycle count by 88 and set LBA to the low bit of that truncated
* result. An even faster (but less accurate) solution would be to mask bit 6 of the CPU cycle count, which will
* doesn't change until the count has been incremented 64 times. See getVT100LBA() for the chosen implementation.
*/
DC011: { // generates Line Buffer Addresses (LBAs) for the Video Processor
PORT: 0xC2, // write-only
COLS80: 0x00,
COLS132: 0x10,
RATE60: 0x20,
RATE50: 0x30,
INITCOLS: 0x00, // ie, COLS80
INITRATE: 0x20 // ie, RATE60
},
/*
* DC012 is referred to as a Control Chip.
@ -302,40 +330,33 @@ ChipSet.VT100 = {
*/
DC012: { // generates scan counts for the Video Processor
PORT: 0xA2, // write-only
INIT: 0x00 // for lack of a better guess
SCROLL_LO: 0x00,
INITSCROLL: 0x00,
INITBLINK: 0x00,
INITREVERSE:0x00,
INITATTR: 0x00
},
/*
* DC011 is referred to as a Timing Chip.
*
* As p. 4-55 (105) of the VT100 Technical Manual (July 1982) explains:
*
* The DCO11 is a custom designed bipolar circuit that provides most of the timing signals required by the
* video processor. Internal counters divide the output of a 24.0734 MHz oscillator (located elsewhere on the
* terminal controller module) into the lower frequencies that define dot, character, scan, and frame timing.
* The counters are programmable through various input pins to control the number of characters per line,
* the frequency at which the screen is refreshed, and whether the display is interlaced or noninterlaced.
* These parameters can be controlled through SET-UP mode or by the host.
*
* 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
* 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.
*
* And on p. 4-62, timings are provided for the LBA0 through LBA7 when the VT100 is in 80-column mode; 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)
*
* If we assume that the CPU cycle count increments once every 361.69ns, it will increment roughly 88 times every
* time LBA7 toggles. So we can divide the CPU cycle count by 88 and set LBA to the low bit of that truncated
* result. An even faster (but less accurate) solution would be to mask bit 6 of the CPU cycle count, which will
* doesn't change until the count has been incremented 64 times. See getVT100LBA() for the chosen implementation.
* ER1400 Non-Volatile RAM (NVR) Chip Definitions
*/
NVR: {
LATCH: {
PORT: 0x62 // write-only
},
CMD: {
ACCEPT_DATA: 0x0,
ACCEPT_ADDR: 0x1,
SHIFT_OUT: 0x2,
WRITE: 0x4,
ERASE: 0x5,
READ: 0x6,
STANDBY: 0x7
},
WORDMASK: 0x3fff // NVR words are 14-bit
/*
* The Technical Manual, p. 4-18, also notes that "Early VT100s can disable the receiver interrupt by
* programming D4 in the NVR latch. However, this is never used by the VT100."
*/
DC011: { // generates Line Buffer Addresses (LBAs) for the Video Processor
PORT: 0xC2, // write-only
INIT: 0x00 // for lack of a better guess
}
};
@ -402,7 +423,8 @@ ChipSet.prototype.initBus = function(cmp, bus, cpu, dbg)
this.cpu = cpu;
this.dbg = dbg;
this.cmp = cmp;
this.kbd = cmp.getMachineComponent("Keyboard");
this.kbd = /** @type {Keyboard} */ (cmp.getMachineComponent("Keyboard"));
this.video = /** @type {Video} */ (cmp.getMachineComponent("Video"));
bus.addPortInputTable(this, this.config.portsInput);
bus.addPortOutputTable(this, this.config.portsOutput);
};
@ -452,9 +474,17 @@ ChipSet.SI1978.INIT = [
ChipSet.VT100.INIT = [
[
ChipSet.VT100.BRIGHTNESS.INIT,
ChipSet.VT100.FLAGS_BUFFER.NO_AVO | ChipSet.VT100.FLAGS_BUFFER.NO_GFX,
ChipSet.VT100.DC012.INIT,
ChipSet.VT100.DC011.INIT
ChipSet.VT100.FLAGS_BUFFER.NO_AVO | ChipSet.VT100.FLAGS_BUFFER.NO_GFX
],
[
ChipSet.VT100.DC011.INITCOLS,
ChipSet.VT100.DC011.INITRATE
],
[
ChipSet.VT100.DC012.INITSCROLL,
ChipSet.VT100.DC012.INITBLINK,
ChipSet.VT100.DC012.INITREVERSE,
ChipSet.VT100.DC012.INITATTR
],
[
0, 0, 0, 0,
@ -501,8 +531,10 @@ 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, this.bDC012, this.bDC011]);
state.set(1, [this.dNVRAddr, this.wNVRData, this.bNVRLatch, this.bNVROut, this.aNVRWords]);
state.set(0, [this.bBrightness, this.bFlagsBuffer]);
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]);
break;
}
return state.data();
@ -534,9 +566,15 @@ ChipSet.prototype.restore = function(data)
case ChipSet.VT100.MODEL:
this.bBrightness = a[0];
this.bFlagsBuffer = a[1];
this.bDC012 = a[2];
this.bDC011 = a[3];
a = data[1];
this.bDC011Cols = a[0];
this.bDC011Rate = a[1];
a = data[2];
this.bDC012Scroll = a[0];
this.bDC012Blink = a[1];
this.bDC012Reverse = a[2];
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
@ -909,9 +947,8 @@ ChipSet.prototype.outVT100NVRLatch = function(port, b, addrFrom)
/**
* outVT100DC012(port, b, addrFrom)
*
* TODO: Consider whether we should disable any interrupts (eg, vertical retrace) until the
* this port is initialized at runtime. We initialize it ourselves at start-up, but our initial
* value is just a guess.
* TODO: Consider whether we should disable any interrupts (eg, vertical retrace) until
* this port is initialized at runtime.
*
* @this {ChipSet}
* @param {number} port (0xA2)
@ -921,7 +958,34 @@ ChipSet.prototype.outVT100NVRLatch = function(port, b, addrFrom)
ChipSet.prototype.outVT100DC012 = function(port, b, addrFrom)
{
this.printMessageIO(port, b, addrFrom, "DC012");
this.bDC012 = b;
var bOpt = b & 0x3;
var bCmd = (b >> 2) & 0x3;
switch(bCmd) {
case 0x0:
this.bDC012Scroll = (this.bDC012Scroll & ~0x3) | bOpt;
break;
case 0x1:
this.bDC012Scroll = (this.bDC012Scroll & ~0xC) | (bOpt << 2);
break;
case 0x2:
switch(bOpt) {
case 0x0:
this.bDC012Blink = ~this.bDC012Blink;
break;
case 0x1:
// TODO: Clear vertical frequency interrupt
break;
case 0x2:
case 0x3:
this.bDC012Reverse = 0x3 - bOpt;
break;
}
break;
case 0x3:
this.bDC012Attr = bOpt;
break;
}
};
/**
@ -935,7 +999,25 @@ ChipSet.prototype.outVT100DC012 = function(port, b, addrFrom)
ChipSet.prototype.outVT100DC011 = function(port, b, addrFrom)
{
this.printMessageIO(port, b, addrFrom, "DC011");
this.bDC011 = b;
if (b & ChipSet.VT100.DC011.RATE60) {
b &= ChipSet.VT100.DC011.RATE50;
if (this.bDC011Rate != b) {
this.bDC011Rate = b;
if (this.video) {
this.video.updateRate(this.bDC011Rate == ChipSet.VT100.DC011.RATE50? 50 : 60);
}
}
} else {
b &= ChipSet.VT100.DC011.COLS132;
if (this.bDC011Cols != b) {
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);
this.video.updateDimensions(nCols, nRows);
}
}
}
};
/*

View file

@ -176,13 +176,13 @@ CPU.prototype.initBus = function(cmp, bus, cpu, dbg)
/*
* We need to know the refresh rate (and corresponding interrupt rate, if any) of the Video component.
*/
var video = cmp.getMachineComponent("Video");
var video = /** @type {Video} */ (cmp.getMachineComponent("Video"));
this.refreshRate = video && video.getRefreshRate() || CPU.VIDEO_UPDATES_PER_SECOND;
/*
* Attach the ChipSet component to the CPU so that it can be notified whenever the CPU stops and starts.
*/
this.chipset = cmp.getMachineComponent("ChipSet");
this.chipset = /** @type {ChipSet} */ (cmp.getMachineComponent("ChipSet"));
/*
* We've already saved the parmsCPU 'autoStart' setting, but there may be a machine (or URL) override.

View file

@ -72,9 +72,9 @@ Component.subclass(Keyboard);
/**
* Alphanumeric and other common (printable) ASCII codes.
*
* TODO: Determine what we can do to get ALL constants like these inlined (enum doesn't seem to
* get the job done); the problem seems to be limited to property references that use quotes, which
* is why I've 'unquoted' as many of them as possible.
* TODO: Determine what we can do to get ALL constants like these inlined by the Closure Compiler
* (enum doesn't seem to get the job done); the problem seems to be limited to property references
* that use quotes, which is why I've 'unquoted' as many of them as possible.
*
* @enum {number}
*/
@ -101,8 +101,8 @@ Keyboard.ASCII = {
*
* keyCodes for most common ASCII keys can simply use the appropriate ASCII code above.
*
* Most of these represent non-ASCII keys (eg, the LEFT arrow key), yet for some reason, browsers defined
* them using ASCII codes (eg, the LEFT arrow key uses the ASCII code for '%' or 37).
* Most of these represent non-ASCII characters (eg, the LEFT arrow key), yet for some reason, browsers
* defined them using ASCII codes (eg, the LEFT arrow key uses 37, which is the ASCII code for '%').
*
* @enum {number}
*/
@ -282,13 +282,15 @@ Keyboard.VT100 = {
KEYMAP: {},
ALTCODES: {},
LEDCODES: {},
SOFTCODES: {},
SOFTCODES: {
'setup': Keyboard.KEYCODE.F9
},
/*
* Reading port 0x82 returns a key address from the VT100 keyboard's UART data output.
*
* Every time a keyboard scan is initiated (by setting the START bit of the status byte),
* an internal address index is reset to zero, and an interrupt is generated for each entry
* in the aKeysActive array, along with a final interrupt for KEYLAST.
* our internal address index (iKeyNext) is set to zero, and an interrupt is generated for
* each entry in the aKeysActive array, along with a final interrupt for KEYLAST.
*/
ADDRESS: {
PORT: 0x82,
@ -320,9 +322,12 @@ Keyboard.VT100 = {
CLICK: 0x80,
INIT: 0x00
},
KEYLAST: 0x7F
KEYLAST: 0x7F // special end-of-scan key address (all valid key addresses are < KEYLAST)
};
/*
* Table to map host key codes to VT100 key addresses (ie, unique 7-bit values representing key positions on the VT100)
*/
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.DEL] = 0x03;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.P] = 0x05;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.O] = 0x06;
@ -486,16 +491,25 @@ Keyboard.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
default:
if (this.config.SOFTCODES && this.config.SOFTCODES[sBinding] !== undefined) {
this.bindings[id] = control;
var fnDown = function(kbd, sSoftCode) {
return function onMouseOrTouchDownKeyboard(event) {
kbd.onSoftKeyDown(sSoftCode, true);
var fnDown = function(kbd, softCode) {
return function onKeyboardBindingDown(event) {
kbd.onSoftKeyDown(softCode, true);
/*
* I'm assuming we only need to give focus back on the "up" event...
*
* if (kbd.cmp) kbd.cmp.updateFocus();
*/
};
}(this, sBinding);
var fnUp = function (kbd, sSoftCode) {
return function onMouseOrTouchUpKeyboard(event) {
kbd.onSoftKeyDown(sSoftCode, false);
}(this, this.config.SOFTCODES[sBinding]);
var fnUp = function (kbd, softCode) {
return function onKeyboardBindingUp(event) {
kbd.onSoftKeyDown(softCode, false);
/*
* Give focus back to the machine (since clicking the button takes focus away).
*/
if (kbd.cmp) kbd.cmp.updateFocus();
};
}(this, sBinding);
}(this, this.config.SOFTCODES[sBinding]);
if ('ontouchstart' in window) {
control.ontouchstart = fnDown;
control.ontouchend = fnUp;
@ -522,9 +536,10 @@ Keyboard.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
*/
Keyboard.prototype.initBus = function(cmp, bus, cpu, dbg)
{
this.cmp = cmp;
this.cpu = cpu;
this.dbg = dbg; // NOTE: The "dbg" property must be set for the message functions to work
this.chipset = cmp.getMachineComponent("ChipSet");
this.chipset = /** @type {ChipSet} */ (cmp.getMachineComponent("ChipSet"));
bus.addPortInputTable(this, this.config.portsInput);
bus.addPortOutputTable(this, this.config.portsOutput);
};
@ -873,8 +888,8 @@ Keyboard.prototype.checkBusy = function()
*
* 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.
* in the VT100.KEYMAP, latch it, and increment iKeyNext. Failing that, we latch Keyboard.VT100.KEYLAST
* and reset iKeyNext to -1.
*
* @this {Keyboard}
* @param {number} port (0x82)
@ -893,8 +908,8 @@ Keyboard.prototype.inVT100UARTAddress = function(port, addrFrom)
/*
* In MAXDEBUG builds, this code removes the key as soon as it's been reported, because
* when debugging, it's easy for the window to lose focus and never receive the keyUp event,
* thereby leaving us with a stuck key. However, this causes more problems than it solves,
* and seems to illustrate that key presses need to be persist for more than a single poll.
* thereby leaving us with a stuck key. However, this may cause more problems than it solves,
* because the VT100's ROM seems to require that key presses persist for more than a single poll.
*/
this.aKeysActive.splice(this.iKeyNext, 1);
}

View file

@ -95,7 +95,7 @@ Panel.prototype.initBus = function(cmp, bus, cpu, dbg)
this.bus = bus;
this.cpu = cpu;
this.dbg = dbg;
this.kbd = cmp.getMachineComponent("Keyboard");
this.kbd = /** @type {Keyboard} */ (cmp.getMachineComponent("Keyboard"));
};
/**

View file

@ -136,6 +136,90 @@ function SerialPort(parmsSerial) {
Component.subclass(SerialPort);
SerialPort.UART8251 = {
/*
* Format of MODE byte written to CONTROL port 0x1
*/
MODE: {
BAUD_FACTOR: 0x03, // 00=SYNC, 01=1x, 10=16x, 11=64x
DATA_BITS: 0x0C, // 00=5, 01=6, 10=7, 11=8
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
},
/*
* Format of COMMAND byte written to CONTROL port 0x1
*/
COMMAND: {
XMIT_ENABLE: 0x01,
DTR: 0x02, // Data Terminal Ready
RECV_ENABLE: 0x04,
SEND_BREAK: 0x08,
ERROR_RESET: 0x10,
RTS: 0x20, // Request To Send
INTERNAL_RESET: 0x40,
HUNT_MODE: 0x80,
INIT: 0x27 // XMIT_ENABLE | DTR | RECV_ENABLE | RTS
},
/*
* Format of STATUS byte read from CONTROL port 0x1
*/
STATUS: {
XMIT_READY: 0x01,
RECV_FULL: 0x02,
XMIT_EMPTY: 0x04,
PARITY_ERROR: 0x08,
OVERRUN_ERROR: 0x10,
FRAMING_ERROR: 0x20,
BREAK_DETECT: 0x40,
DSR: 0x80, // Data Set Ready
INIT: 0x85 // XMIT_READY | XMIT_EMPTY | DSR
},
/*
* Format of BAUDRATE 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:
*
* Index Divisor Baud Rate
* ----- ------- ---------
* 0x0 3456 50
* 0x1 2304 75
* 0x2 1571 110
* 0x3 1285 134.5
* 0x4 1152 150
* 0x5 864 200
* 0x6 576 300
* 0x7 288 600
* 0x8 144 1200
* 0x9 96 1800
* 0xA 86 2000
* 0xB 72 2400
* 0xC 48 3600
* 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,
INIT: 0xEE // default to 9600 (0xE) for both XMIT and RECV
}
};
SerialPort.UART8251.INIT = [
false,
0,
0,
SerialPort.UART8251.STATUS.INIT,
SerialPort.UART8251.MODE.INIT,
SerialPort.UART8251.COMMAND.INIT,
SerialPort.UART8251.BAUDRATE.INIT
];
/*
* Internal name used for the I/O buffer control, if any, that we bind to the SerialPort.
*
@ -188,7 +272,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.sendRBR([keyCode]);
serial.sendByteIn(keyCode);
}
return true;
};
@ -218,10 +302,31 @@ SerialPort.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
* so that the soft keyboard will activate, but it shouldn't hurt to remove the attribute for all browsers.
*/
control.removeAttribute("readonly");
return true;
default:
if (sValue) {
/*
* Instead of just having a dedicated "test" control, we now treat any unrecognized control with
* a "value" attribute as a test control. The only caveat is that such controls must have binding IDs
* that do not conflict with predefined controls (which, of course, is the only way you can get here).
*/
this.bindings[sBinding] = control;
/*
* Convert any "backslashed" sequences into the appropriate control characters.
*/
sValue = sValue.replace(/\\n/g, "\n").replace(/\\r/g, "\r");
control.onclick = function onClickTest(event) {
serial.sDataIn = sValue;
serial.sendDataIn();
/*
* Give focus back to the machine (since clicking the button takes focus away).
*/
if (serial.cmp) serial.cmp.updateFocus();
return true;
};
return true;
}
break;
}
return false;
@ -238,10 +343,11 @@ SerialPort.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
*/
SerialPort.prototype.initBus = function(cmp, bus, cpu, dbg)
{
this.cmp = cmp;
this.bus = bus;
this.cpu = cpu;
this.dbg = dbg;
this.chipset = cmp.getMachineComponent("ChipSet");
this.chipset = /** @type {ChipSet} */ (cmp.getMachineComponent("ChipSet"));
bus.addPortInputTable(this, SerialPort.aPortInput, this.portBase);
bus.addPortOutputTable(this, SerialPort.aPortOutput, this.portBase);
this.setReady();
@ -330,11 +436,15 @@ SerialPort.prototype.initState = function(data)
{
var i = 0;
if (data === undefined) {
data = [0, 0, 0];
data = SerialPort.UART8251.INIT;
}
this.bData = data[i++];
this.fReady = data[i++];
this.bDataIn = data[i++];
this.bDataOut = data[i++];
this.bStatus = data[i++];
this.bMode = data[i++];
this.bCommand = data[i++];
this.bBaudRate = data[i++];
this.bBaudRate = data[i];
return true;
};
@ -348,82 +458,46 @@ SerialPort.prototype.saveRegisters = function()
{
var i = 0;
var data = [];
data[i++] = this.bData;
data[i++] = this.fReady;
data[i++] = this.bDataIn;
data[i++] = this.bDataOut;
data[i++] = this.bStatus;
data[i++] = this.bMode;
data[i++] = this.bCommand;
data[i++] = this.bBaudRate;
data[i] = this.bBaudRate;
return data;
};
/**
* inData(port, addrFrom)
* sendByteIn(b)
*
* @this {SerialPort}
* @param {number} port (0x0)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number} simulated port value
* @param {number} b
* @return {boolean}
*/
SerialPort.prototype.inData = function(port, addrFrom)
SerialPort.prototype.sendByteIn = function(b)
{
var b = this.bData;
this.printMessageIO(port, null, addrFrom, "DATA", b);
return b;
if (!(this.bStatus & SerialPort.UART8251.STATUS.RECV_FULL)) {
this.bDataIn = b;
this.bStatus |= SerialPort.UART8251.STATUS.RECV_FULL;
this.cpu.requestINTR(this.nIRQ);
return true;
}
return false;
};
/**
* inCommand(port, addrFrom)
* sendDataIn()
*
* @this {SerialPort}
* @param {number} port (0x1)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number} simulated port value
*/
SerialPort.prototype.inCommand = function(port, addrFrom)
SerialPort.prototype.sendDataIn = function()
{
var b = this.bCommand;
this.printMessageIO(port, null, addrFrom, "COMMAND", b);
return b;
};
/**
* outData(port, bOut, addrFrom)
*
* @this {SerialPort}
* @param {number} port (0x0)
* @param {number} bOut
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
*/
SerialPort.prototype.outData = function(port, bOut, addrFrom)
{
this.printMessageIO(port, bOut, addrFrom, "DATA");
this.bData = bOut;
};
/**
* outCommand(port, bOut, addrFrom)
*
* @this {SerialPort}
* @param {number} port (0x1)
* @param {number} bOut
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
*/
SerialPort.prototype.outCommand = function(port, bOut, addrFrom)
{
this.printMessageIO(port, bOut, addrFrom, "COMMAND");
this.bCommand = bOut;
};
/**
* outBaudRate(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)
{
this.printMessageIO(port, bOut, addrFrom, "BAUDRATE");
this.bBaudRate = bOut;
if (this.sDataIn) {
if (this.sendByteIn(this.sDataIn.charCodeAt(0))) {
this.sDataIn = this.sDataIn.substr(1);
}
}
};
/**
@ -474,12 +548,99 @@ SerialPort.prototype.echoByte = function(b)
return false;
};
/**
* inData(port, addrFrom)
*
* @this {SerialPort}
* @param {number} port (0x0)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number} simulated port value
*/
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;
};
/**
* inControl(port, addrFrom)
*
* @this {SerialPort}
* @param {number} port (0x1)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number} simulated port value
*/
SerialPort.prototype.inControl = function(port, addrFrom)
{
var b = this.bStatus;
this.printMessageIO(port, null, addrFrom, "STATUS", b);
return b;
};
/**
* outData(port, bOut, addrFrom)
*
* @this {SerialPort}
* @param {number} port (0x0)
* @param {number} bOut
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
*/
SerialPort.prototype.outData = function(port, bOut, addrFrom)
{
this.printMessageIO(port, bOut, addrFrom, "DATA");
this.bDataOut = bOut;
};
/**
* outControl(port, bOut, addrFrom)
*
* Writes to the CONTROL port (0x1) are either MODE or COMMAND bytes. If the device has just
* been powered or reset, it is in a "not ready" state and is waiting for a MODE byte. Once it
* has received that initial byte, the device is marked "ready", and all further bytes are
* interpreted as COMMAND bytes (until/unless a COMMAND byte with the INTERNAL_RESET bit is set).
*
* @this {SerialPort}
* @param {number} port (0x1)
* @param {number} bOut
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
*/
SerialPort.prototype.outControl = function(port, bOut, addrFrom)
{
this.printMessageIO(port, bOut, addrFrom, "CONTROL");
if (!this.fReady) {
this.bMode = bOut;
this.fReady = true;
} else {
this.bCommand = bOut;
if (this.bCommand & SerialPort.UART8251.COMMAND.INTERNAL_RESET) {
this.fReady = false;
}
}
};
/**
* outBaudRate(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)
{
this.printMessageIO(port, bOut, addrFrom, "BAUDRATE");
this.bBaudRate = bOut;
};
/*
* Port input notification table
*/
SerialPort.aPortInput = {
0x0: SerialPort.prototype.inData,
0x1: SerialPort.prototype.inCommand
0x1: SerialPort.prototype.inControl
};
@ -488,7 +649,7 @@ SerialPort.aPortInput = {
*/
SerialPort.aPortOutput = {
0x0: SerialPort.prototype.outData,
0x1: SerialPort.prototype.outCommand,
0x1: SerialPort.prototype.outControl,
0x2: SerialPort.prototype.outBaudRate
};

View file

@ -152,7 +152,7 @@ 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 simplify smooth-scrolling down the road.
* row for text modes, to account for the VT100's scroll line buffer (used for smooth scrolling).
*/
if (this.cyCell > 1) this.nRowsBuffer++;
@ -292,6 +292,9 @@ Video.VT100 = {
/**
* initBuffers()
*
* @this {Video}
* @return {boolean}
*/
Video.prototype.initBuffers = function()
{
@ -308,10 +311,11 @@ Video.prototype.initBuffers = function()
cyBuffer = this.cxBuffer;
}
this.sizeBuffer = ((this.cxBuffer * this.nBitsPerPixel) >> 3) * this.cyBuffer;
this.sizeBuffer = 0;
if (!this.fUseRAM) {
this.sizeBuffer = ((this.cxBuffer * this.nBitsPerPixel) >> 3) * this.cyBuffer;
if (!this.bus.addMemory(this.addrBuffer, this.sizeBuffer, Memory.TYPE.VIDEO)) {
return;
return false;
}
}
@ -319,15 +323,15 @@ Video.prototype.initBuffers = function()
* imageBuffer is only used for graphics modes. For text modes, we create a canvas
* for each font and draw characters by drawing from the font canvas to the target canvas.
*/
if (this.cxCell > 1) {
if (this.sizeBuffer) {
this.imageBuffer = this.contextScreen.createImageData(cxBuffer, cyBuffer);
this.nPixelsPerCell = (16 / this.nBitsPerPixel)|0;
this.initCellCache(this.sizeBuffer >> 1);
} else {
/*
* 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;
this.initCellCache(this.sizeBuffer >> 1);
}
this.canvasBuffer = document.createElement("canvas");
@ -383,6 +387,7 @@ Video.prototype.initBuffers = function()
this.abLineBuffer = new Array(this.nColsBuffer);
}
return true;
};
/**
@ -410,7 +415,7 @@ Video.prototype.initBus = function(cmp, bus, cpu, dbg)
* If we have an associated keyboard, then ensure that the keyboard will be notified
* whenever the canvas gets focus and receives input.
*/
this.kbd = cmp.getMachineComponent("Keyboard");
this.kbd = /** @type {Keyboard} */ (cmp.getMachineComponent("Keyboard"));
if (this.kbd) {
for (var s in this.ledBindings) {
this.kbd.setBinding("led", s, this.ledBindings[s]);
@ -461,7 +466,8 @@ Video.prototype.doneLoad = function(sURL, sFontData, nErrorCode)
* Minimal font data validation, just to make sure we're not getting garbage from the server.
*/
if (abFontData.length == 2048) {
this.createFonts(abFontData);
this.abFontData = abFontData;
this.createFonts();
}
else {
this.notice("Unrecognized font data length (" + abFontData.length + ")");
@ -484,17 +490,17 @@ Video.prototype.doneLoad = function(sURL, sFontData, nErrorCode)
};
/**
* createFonts(abFontData)
* createFonts()
*
* @this {Video}
* @param {Array.<number>} abFontData
* @return {boolean}
*/
Video.prototype.createFonts = function(abFontData)
Video.prototype.createFonts = function()
{
/*
* We retain abFontData in case we have to rebuild the fonts (eg, when we switch from 80 to 132 columns)
*/
this.abFontData = abFontData;
if (this.abFontData) {
this.fDotStretcher = (this.nFormat == Video.FORMAT.VT100);
this.aFonts[Video.VT100.FONT.NORML] = [
this.createFontVariation(this.cxCell, this.cyCell),
@ -508,6 +514,9 @@ Video.prototype.createFonts = function(abFontData)
this.createFontVariation(this.cxCell*2, this.cyCell*2),
this.createFontVariation(this.cxCell*2, this.cyCell*2, this.fUnderline)
];
return true;
}
return false;
};
/**
@ -712,6 +721,47 @@ Video.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
return false;
};
/**
* updateDimensions(nCols, nRows)
*
* Called from the ChipSet component whenever the screen dimensions have been dynamically altered.
*
* @this {Video}
* @param {number} nCols (should be either 80 or 132; 80 is the default)
* @param {number} nRows (should be either 24 or 14; 24 is the default)
*/
Video.prototype.updateDimensions = function(nCols, nRows)
{
this.printMessage("updateDimensions(" + nCols + "," + nRows + ")");
this.nColsBuffer = nCols;
/*
* Even when the number of effective rows is 14 (or 15 counting the scroll line buffer), we want
* to leave the number of rows at 24 (or 25 counting the scroll line buffer), because the VT100 doesn't
* actually change character height (only character width).
*
* this.nRowsBuffer = nRows+1; // +1 for scroll line buffer
*/
this.cxCell = this.cxCellDefault;
if (nCols > 80) this.cxCell--; // VT100 font cells are 9x10 instead of 10x10 in 132-column mode
if (this.initBuffers()) {
this.createFonts();
}
};
/**
* updateRate(nRate)
*
* Called from the ChipSet component whenever the monitor refresh rate has been dynamically altered.
*
* @this {Video}
* @param {number} nRate (should be either 50 or 60; 60 is the default)
*/
Video.prototype.updateRate = function(nRate)
{
this.printMessage("updateRate(" + nRate + ")");
this.rateMonitor = nRate;
};
/**
* doFullScreen()
*
@ -796,7 +846,7 @@ Video.prototype.notifyFullScreen = function(fFullScreen)
this.canvasScreen.style.width = this.canvasScreen.style.height = "";
}
}
this.printMessage("notifyFullScreen(" + fFullScreen + ")", true);
this.printMessage("notifyFullScreen(" + fFullScreen + ")");
};
/**
@ -1057,8 +1107,8 @@ Video.prototype.updateVT100 = function(fForced)
if (cUpdated && this.contextBuffer) {
/*
* NOTE: We must subtract cyCell from cyBuffer to avoid displaying the extra row that we normally buffer
* in support of smooth-scrolling.
* NOTE: We must subtract cyCell from cyBuffer to avoid displaying the extra "scroll line" that we normally
* buffer to support smooth-scrolling.
*/
this.contextScreen.drawImage(this.canvasBuffer, 0, 0, this.cxBuffer, this.cyBuffer - this.cyCell, this.xScreenOffset, this.yScreenOffset, this.cxScreenOffset, this.cyScreenOffset);
}
@ -1111,9 +1161,10 @@ Video.prototype.updateScreen = function(n)
}
/*
* Since this is not a forced update, if our cell cache is valid AND the buffer is clean, then do nothing.
* Since this is not a forced update, if our cell cache is valid AND we allocated our own buffer AND the buffer
* is clean, then there's nothing to do.
*/
if (fUpdate && this.fCellCacheValid) {
if (fUpdate && this.fCellCacheValid && this.sizeBuffer) {
if ((fClean = this.bus.cleanMemory(this.addrBuffer, this.sizeBuffer))) {
fUpdate = false;
}

View file

@ -1220,7 +1220,7 @@ Keyboard.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
if (Keyboard.CLICKCODES[sCode] !== undefined && sHTMLType == "button") {
this.bindings[id] = control;
control.onclick = function(kbd, sKey, simCode) {
return function onClickKeyboard(event) {
return function onKeyboardBindingClick(event) {
if (!COMPILED && kbd.messageEnabled()) kbd.printMessage(sKey + " clicked", Messages.KEYS);
if (kbd.cmp) kbd.cmp.updateFocus();
kbd.updateShiftState(simCode, true); // future-proofing if/when any LOCK keys are added to CLICKCODES
@ -1233,12 +1233,12 @@ Keyboard.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
this.cSoftCodes++;
this.bindings[id] = control;
var fnDown = function(kbd, sKey, simCode) {
return function onMouseOrTouchDownKeyboard(event) {
return function onKeyboardBindingDown(event) {
kbd.addActiveKey(simCode);
};
}(this, sBinding, Keyboard.SOFTCODES[sBinding]);
var fnUp = function (kbd, sKey, simCode) {
return function onMouseOrTouchUpKeyboard(event) {
return function onKeyboardBindingUp(event) {
kbd.removeActiveKey(simCode);
};
}(this, sBinding, Keyboard.SOFTCODES[sBinding]);
@ -1254,8 +1254,8 @@ Keyboard.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
else if (sValue) {
/*
* Instead of just having a dedicated "test" control, we now treat any unrecognized control with
* a data value as a test control. The only caveat is that such controls must have binding IDs that
* do not conflict with predefined controls (which, of course, is the only way you can get here).
* a "value" attribute as a test control. The only caveat is that such controls must have binding IDs
* that do not conflict with predefined controls (which, of course, is the only way you can get here).
*/
this.bindings[id] = control;
control.onclick = function onClickTest(event) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long