Merge branch 'next-release'

This commit is contained in:
Jeff Parsons 2016-08-14 10:53:54 -07:00
commit 780fb5ee01
11 changed files with 6067 additions and 5866 deletions

View file

@ -1,6 +1,6 @@
---
layout: page
title: DEC VT100 Terminal
title: DEC VT100 Terminal with Debugger
permalink: /devices/pc8080/machine/vt100/debugger/
machines:
- type: pc8080
@ -13,10 +13,22 @@ 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.
[PC8080](/modules/pc8080/) CPU emulator. You'll also find assorted [Hardware Notes](#vt100-memory-usage) below.
Click the "Run" button to start the simulation. You'll also find assorted
[Hardware Notes](#vt100-memory-usage) below.
Click the "Run" button to start the simulation. 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
- F2: PF2
- F3: PF3
- F4: PF4
- F5: KEYPAD COMMA
- F6: BREAK
- F7: LINE FEED
- F8: NO SCROLL
- F9: SET-UP
The simulation is not fully operational yet, but it is now possible to access the SET-UP screen.
{% include machine.html id="vt100" %}

View file

@ -3,7 +3,7 @@
<machine id="vt100" class="pc8080" border="1" pos="center" background="#FAEBD7">
<name pos="center">VT100 Terminal</name>
<computer id="computer" busWidth="16"/>
<cpu id="cpu8080" model="8080" cycles="2764800"/>
<cpu id="cpu8080" model="8080" cycles="1000000"/>
<rom id="rom" addr="0x0000" size="0x2000" file="/devices/pc8080/rom/vt100/VT100.json"/>
<ram id="ram" addr="0x2000" size="0x0C00"/>
<video id="video" screenWidth="1600" screenHeight="960" smoothing="false" interruptRate="60"

View file

@ -3,7 +3,7 @@
<machine id="vt100" class="pc8080" border="1" pos="center" background="#FAEBD7">
<name pos="center">VT100 Terminal</name>
<computer id="computer" busWidth="16"/>
<cpu id="cpu8080" model="8080" cycles="2764800"/>
<cpu id="cpu8080" model="8080" cycles="1000000"/>
<rom id="rom" addr="0x0000" size="0x2000" file="/devices/pc8080/rom/vt100/VT100.json"/>
<ram id="ram" addr="0x2000" size="0x0C00"/>
<video id="video" screenWidth="1600" screenHeight="960" smoothing="false" interruptRate="60"

View file

@ -6,60 +6,75 @@
; work at https://github.com/phooky/VT100-Hax/blob/master/ROMs/haxrom.d80 and http://vt100romhax.tumblr.com
;
; All other comments/annotations by Jeff Parsons (@jeffpar) <Jeff@pcjs.org>
;
;
; Data area definitions
;
stack_top equ 204eh
scratch_start equ 204eh
cursor_countdn equ 212dh
cursor_visible equ 21bah
;
; Reset vector
;
org 0
X0000: di
lxi sp,X204e
jmp X003b
di
lxi sp,stack_top ; SP = 0x204e
jmp reset
;
; Interrupt vector 0x1 for Keyboard
;
org 8
;
; Interrupt vector 0x1 for keyboard
;
X0008: call X00fd
call X00fd
ei
ret
X000d: nop
;
; Interrupt vector 0x2 for PUSART
;
org 10h
call X03cc
ei
X0014: ret
nop
ret
;
; Interrupt vector 0x3 for Keyboard and PUSART
;
org 18h
call X03cc
call X00fd
ei
ret
;
; Interrupt vector 0x4 for Video (vertical retrace)
;
org 20h
call X04cf
ret
nop
;
; Interrupt vector 0x5 for Keyboard and Video
;
org 28h
call X04cf
ret
nop
;
; Interrupt vector 0x6 for PUSART and Video
;
org 30h
X0030: call X03cc
call X04cf
ei
ret
;
; Interrupt vector 0x7 for Keyboard and PUSART and Video
;
org 38h
jmp X0030
X003b: mvi e,1
;
; Beginning of power-up (reset) code at 0x003b
;
reset: mvi e,1
X003d: di
mvi a,0fh
out 62h ;; 0x0f to NVR latch
@ -233,7 +248,7 @@ X00fd: push psw
sui 7ch ;; subtract 0x7c from a
jm X011a ;; jump to 011a if a is a normal key
mov h,a ;; otherwise h <- a
X0109: inr h ;; h++
inr h ;; h++
mvi a,10h ;; a <- 0x10
rrc
X010d: rlc
@ -370,7 +385,7 @@ X01ff: lxi h,X0812
X020e: sui 41h
mov b,a
X0211: mov a,m
X0212: jz X0222
jz X0222
dcr b
jz X0226
dcr b
@ -408,7 +423,7 @@ X0245: mov a,c
jmp X0812
X0251: di
lxi sp,X204e
lxi sp,stack_top ; SP = 0x204e
lxi h,2000h
push h
mov a,m
@ -447,10 +462,10 @@ X0298: call X0bf2
;;
;; Zero all scratch and screen RAM above stack
;;
X02a4: lxi h,X204e ;; HL <- 0x204e (top of stack)
lxi d,X0fb2 ;; DE <- 0x0fb2
mvi b,0 ;; B <- 0
call X1083 ;; memset(0x204e,0,0xfb2)
X02a4: lxi h,scratch_start ;; HL = 0x204e (just above stack)
lxi d,0fb2h ;; DE = 0x0fb2 (every byte from 0x204e up to 0x3000)
mvi b,0 ;; B = 0
call memset ;; memset(0x1083): set 0x0fb2 bytes to 0 starting at 0x204e
cma ;; invert A
sta X2104 ;; store A in 0x2104
lxi h,X2004 ;; HL <- 0x2004
@ -469,7 +484,7 @@ X02c0: call X0a15 ;; 0x2140 = 0xe605
lxi h,X3000 ;; HL = 0x3000
lxi d,X1000 ;; DE = 0x1000
mvi b,0ffh ;; B = 0xFF
jmp X1083 ;; Set all of attribute RAM to 0xFF and return
jmp memset ;; invoke memset(0x1083) to set all of attribute RAM to 0xFF and return
;;
;; 0x02d9 through 0x02eb are the initial values for the screen RAM;
;; it's copied into low RAM at 0x2000 during init.
@ -484,8 +499,8 @@ X02d9:
;;
;; Initialize several scratch values. We don't know what all of these are for yet.
;;
X02eb: lxi h,X0212 ;; HL = 0x0212
shld X212d ;; Store 0x1202 at 0x212d
X02eb: lxi h,212h ;; HL = 0x0212 (530.)
shld cursor_countdn ;; Store 0x0212 at cursor_countdn [0x212d]
mvi a,35h ;; A = 0x35
sta X212c ;; 0x212c = 0x35
mvi a,1 ;; A = 1
@ -504,7 +519,7 @@ X02eb: lxi h,X0212 ;; HL = 0x0212
sta X2079 ;; else store 1 in 0x2079
X031a: mvi a,0ffh ;; A = 0xff
sta X210e ;; 0x210e = 0xff
sta X21ba ;; 0x21ba = 0xff
sta cursor_visible ;; set cursor_visible [0x21ba] to 0xff (initially visible)
mvi h,80h ;; H = 0x80
mov l,h ;; L = 0x80
shld X20c0 ;; Store 0x8080 at 0x20c0
@ -1592,7 +1607,7 @@ X0a7b: inx sp
mvi a,48h
sta X207e
lxi h,X0000
lxi h,0
shld X2130
pop h
X0aa2: lxi h,X0a15
@ -2405,7 +2420,7 @@ X0fae: cmp m
rz
push h
push psw
X0fb2: lda X207b
lda X207b
ora a
cz X1488
pop psw
@ -2415,11 +2430,11 @@ X0fb2: lda X207b
; End of main loop?
;
X0fbe: lhld X204e
X0fbe: lhld scratch_start ; HL = [0x204e]
mov a,h
ori 0f0h
mov h,a
shld X204e
shld scratch_start ; update [0x204e]
X0fc8: lda X2050
X0fcb: mov b,a
mov a,h
@ -2525,15 +2540,14 @@ X1074: call X13e3
shld X214e
ret
;;
;; memset: Set memory from HL to HL+DE-1 to value B
;; memset(0x1083): Set DE bytes at address HL to B
;;
memset:
X1083: mov m,b ;; M <- B
memset: mov m,b ;; M <- B
inx h ;; HL++
dcx d ;; DE--
mov a,d ;;
ora e ;; A = D | E
jnz X1083 ;; repeat if A != 0
jnz memset ;; repeat if A != 0
ret
mvi c,0
@ -2672,7 +2686,7 @@ X1159: mov m,e
X117e: mov a,h
ori 0f0h
mov h,a
shld X204e
shld scratch_start ; update [0x204e]
lxi h,X2113
xra a
X1189: mov m,a
@ -2727,7 +2741,7 @@ X11ce: ora a
mov l,m
ori 0f0h
mov h,a
shld X204e
shld scratch_start ; update [0x204e]
call X1290
pop h
mov b,d
@ -2747,7 +2761,7 @@ X11e8: call X127f
ori 0f0h
mov h,a
mov l,e
shld X204e
shld scratch_start ; update [0x204e]
pop h
mov a,h
ori 9fh
@ -2810,12 +2824,12 @@ X1252: call X129d
mov c,m
call X13de
xchg
lhld X204e
lhld scratch_start ; load [0x204e]
xchg
mov m,e
dcx h
mov m,d
X1271: lhld X204e
X1271: lhld scratch_start ; load [0x204e]
shld X2056
call X1299
mov m,b
@ -3175,25 +3189,29 @@ X14a2: lxi h,X21a5 ;;
lda X2077 ;; load what's at 0x2077
ora a ;; return if it's not zero
rnz ;;
lda X2065 ;; A = 0x2065
lda X2065 ;; A = [0x2065]
lxi h,X2051 ;;
ora m ;; return if [0x2065] | [0x2051] != 0
rnz
lhld X212d
lhld cursor_countdn ; HL = cursor countdown from [0x212d]
dcx h
mov a,h
ora l
jz X14d4
shld X212d
shld cursor_countdn ; update cursor countdown with decremented value
ret
X14d4: lda X21ba
xri 0ffh
sta X21ba
lxi h,X0212
jnz X14e5
lxi h,X0109
X14e5: shld X212d
;
; Cursor countdown reached zero, time to invert the cursor;
; note that the cursor is on (visible) twice as long as it's off
;
X14d4: lda cursor_visible ; A = 0xff (visible) or 0x00 (invisible)
xri 0ffh ; invert
sta cursor_visible ;
lxi h,212h ; new initial count; start with 0x0212
jnz X14e5 ; and stick with that if cursor still visible
lxi h,109h ; otherwise, use half that value (0x0109) if invisible
X14e5: shld cursor_countdn ; store new cursor countdown
lhld X20f6
mov b,m
lda X2159
@ -3394,10 +3412,10 @@ X162b: inx h
inx h
jmp X13db
X1636: lxi h,X000d
shld X212d
X1636: lxi h,13
shld cursor_countdn ; reset cursor countdown to 13 (0x000d)
xra a
sta X21ba
sta cursor_visible ; reset cursor visibility
lhld X20f6
lda X20f4
mov b,a
@ -3679,7 +3697,7 @@ X1837: cmp d
X1844: mov m,a
jmp X1636
X1848: lxi h,X0000
X1848: lxi h,0
shld X2130
mvi a,0ffh
sta X210e
@ -3786,7 +3804,7 @@ X1900: in 42h
out 62h
lxi d,X21d3 ;; DE = 0x21d3
mvi b,0eh ;; B = 14
lxi h,X0000 ;; HL = 0
lxi h,0 ;; HL = 0
X1916: dad h ;;
ldax d ;; load next char
ani 20h ;; and with NVR data bit
@ -3967,7 +3985,7 @@ X1a2b: mvi c,80h
shld X2111
lxi h,X19ee
shld X2140
lxi h,X0000
lxi h,0
shld X2143
shld X2172
X1a45: lhld X2004
@ -4170,7 +4188,7 @@ X1bb0: call X0394
shld X2140
ret
X1bba: lhld X204e
X1bba: lhld scratch_start ; load [0x204e]
jmp X1bc3
X1bc0: lxi h,X21cc
@ -4374,7 +4392,7 @@ X1d17: mvi m,0
lda X21ac
call X1d61
call X1c9f
lxi d,X0008
lxi d,8
mvi c,4
pop psw
jz X1d3d
@ -4600,8 +4618,8 @@ X1e9f: call X1edd
inx h
shld X21b4
mov b,a
lxi d,X0014
call X1083
lxi d,20 ; DE = 20(0x0014)
call memset ; call memset(0x1083)
lxi h,X1eb8
shld X2140
ret
@ -4635,7 +4653,7 @@ X1ee5: call X05e3
ret
X1eea: push h
lxi h,X204e
lxi h,scratch_start ; HL -> 0x204e
mov c,a
lda X21a2
ora a
@ -4804,7 +4822,6 @@ X1ccd equ 1ccdh
X2001 equ 2001h
X2002 equ 2002h
X2004 equ 2004h
X204e equ 204eh
X2050 equ 2050h
X2051 equ 2051h
X2052 equ 2052h
@ -4818,7 +4835,7 @@ X2065 equ 2065h
X2067 equ 2067h
X2068 equ 2068h ; Keys flag buffer
X2069 equ 2069h
X206a equ 206ah
X206a equ 206ah ; Start of the "three place New Key Address Buffer"
X206e equ 206eh
X2072 equ 2072h
X2073 equ 2073h
@ -4857,7 +4874,6 @@ X2111 equ 2111h
X2113 equ 2113h
X212b equ 212bh
X212c equ 212ch
X212d equ 212dh
X212f equ 212fh
X2130 equ 2130h
X2131 equ 2131h
@ -4912,7 +4928,6 @@ X21af equ 21afh
X21b4 equ 21b4h
X21b8 equ 21b8h
X21b9 equ 21b9h
X21ba equ 21bah
X21bb equ 21bbh
X21bc equ 21bch
X21bd equ 21bdh

File diff suppressed because it is too large Load diff

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):
@ -386,6 +402,7 @@ ChipSet.prototype.initBus = function(cmp, bus, cpu, dbg)
this.cpu = cpu;
this.dbg = dbg;
this.cmp = cmp;
this.kbd = cmp.getMachineComponent("Keyboard");
bus.addPortInputTable(this, this.config.portsInput);
bus.addPortOutputTable(this, this.config.portsOutput);
};
@ -728,7 +745,7 @@ ChipSet.prototype.outSIWatchdog = function(port, b, addrFrom)
};
/**
* getVT100LBA(nBit)
* getVT100LBA(iBit)
*
* Returns the state of the requested (simulated) LBA bit.
*
@ -737,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;
};
/**
@ -852,6 +869,10 @@ ChipSet.prototype.inVT100FlagsBuffer = function(port, addrFrom)
if (this.bNVROut) {
b |= ChipSet.VT100.FLAGS_BUFFER.NVR_DATA;
}
b &= ~ChipSet.VT100.FLAGS_BUFFER.KBD_XMIT;
if (this.kbd && !this.kbd.checkBusy()) {
b |= ChipSet.VT100.FLAGS_BUFFER.KBD_XMIT;
}
this.bFlagsBuffer = b;
this.printMessageIO(port, null, addrFrom, "FLAGS.BUFFER", b);
return b;
@ -888,6 +909,10 @@ 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.
*
* @this {ChipSet}
* @param {number} port (0xA2)
* @param {number} b

View file

@ -939,6 +939,7 @@ CPUState.prototype.checkINTR = function()
{
if ((this.intFlags & CPUDef.INTFLAG.INTR) && this.getIF()) {
var bRST = CPUDef.OPCODE.RST0 | ((this.intFlags & CPUDef.INTFLAG.INTL) << 3);
this.intFlags &= ~CPUDef.INTFLAG.HALT;
this.clearINTR();
this.clearIF();
this.aOps[bRST].call(this);
@ -1096,16 +1097,30 @@ CPUState.prototype.stepCPU = function(nMinCycles)
do {
if (this.intFlags) {
if (this.checkINTR()) {
if (!nMinCycles) {
this.assert(DEBUGGER); // nMinCycles of zero should be generated ONLY by the Debugger
if (DEBUGGER) {
this.println("interrupt dispatched");
break;
/*
* We no longer call checkINTR() if the Debugger is single-stepping; you'll have to let the
* CPU run with a "g" (or a "p" on a call instruction) if you want interrupts to be processed.
*/
if (nMinCycles) {
/*
* NOTE: If checkINTR() returns true, it also clears INTFLAG.HALT, so we don't have to worry
* about the INTFLAG.HALT code below triggering.
*/
this.checkINTR();
/*
* If the Debugger is running, consider some new notification mechanism(s) regarding interrupt
* dispatches; the following code no longer applies, due to changes above.
*
* if (!nMinCycles && this.checkINTR()) {
* this.assert(DEBUGGER); // nMinCycles of zero should be generated ONLY by the Debugger
* if (DEBUGGER) {
* this.println("interrupt dispatched");
* break;
* }
* }
*/
}
}
}
else if (this.intFlags & CPUDef.INTFLAG.HALT) {
if (this.intFlags & CPUDef.INTFLAG.HALT) {
/*
* As discussed in opHLT(), the CPU is never REALLY halted by a HLT instruction; instead,
* opHLT() sets CPUDef.INTFLAG.HALT, signalling to us that we're free to end the current burst

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.
@ -286,10 +286,11 @@ Keyboard.VT100 = {
*
* 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 aKeysPressed array, along with a final interrupt for KEYLAST.
* 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.
@ -329,39 +330,56 @@ Keyboard.VT100.KEYMAP[Keyboard.ASCII.W] = 0x09;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.Q] = 0x0A;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.RIGHT] = 0x10;
Keyboard.VT100.KEYMAP[Keyboard.ASCII[']']] = 0x14;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['}']] = 0x94;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['[']] = 0x15;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['{']] = 0x95;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.I] = 0x16;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.U] = 0x17;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.R] = 0x18;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.E] = 0x19;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['1']] = 0x1A;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['!']] = 0x9A;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.LEFT] = 0x20;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.DOWN] = 0x22;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.F6] = 0x23; // aka BREAK
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.PAUSE] = 0x23; // aka BREAK
Keyboard.VT100.KEYMAP[Keyboard.ASCII['`']] = 0x24;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['~']] = 0xA4;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['-']] = 0x25;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['_']] = 0xA5;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['9']] = 0x26;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['(']] = 0xA6;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['7']] = 0x27;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['&']] = 0xA7;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['4']] = 0x28;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['$']] = 0xA8;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['3']] = 0x29;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['#']] = 0xA9;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.ESC] = 0x2A;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.UP] = 0x30;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.F3] = 0x31; // aka PF3
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.F1] = 0x32; // aka PF1
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.BS] = 0x33;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['=']] = 0x34;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['+']] = 0xB4;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['0']] = 0x35;
Keyboard.VT100.KEYMAP[Keyboard.ASCII[')']] = 0xB5;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['8']] = 0x36;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['*']] = 0xB6;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['6']] = 0x37;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['^']] = 0xB7;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['5']] = 0x38;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['%']] = 0xB8;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['2']] = 0x39;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['@']] = 0xB9;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.TAB] = 0x3A;
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;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.K] = 0x47;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.G] = 0x48;
@ -372,33 +390,38 @@ Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.NUM_CR] = 0x51;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.NUM_2] = 0x52;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.NUM_1] = 0x53;
Keyboard.VT100.KEYMAP[Keyboard.ASCII["'"]] = 0x55;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['"']] = 0xD5;
Keyboard.VT100.KEYMAP[Keyboard.ASCII[';']] = 0x56;
Keyboard.VT100.KEYMAP[Keyboard.ASCII[':']] = 0xD6;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.J] = 0x57;
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.F8] = 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
Keyboard.VT100.KEYMAP[Keyboard.ASCII['.']] = 0x65;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['>']] = 0xE5;
Keyboard.VT100.KEYMAP[Keyboard.ASCII[',']] = 0x66;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['<']] = 0xE6;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.N] = 0x67;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.B] = 0x68;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.X] = 0x69;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.F9] = 0x6A; // aka NO SCROLL
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;
Keyboard.VT100.KEYMAP[Keyboard.ASCII[' ']] = 0x77;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.V] = 0x78;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.C] = 0x79;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.Z] = 0x7A;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.F10] = 0x7B; // aka SET-UP
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.F9] = 0x7B; // aka SET-UP
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.CTRL] = 0x7C;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.SHIFT] = 0x7D; // either shift key (doesn't matter)
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.CAPSLOCK]= 0x7E;
@ -560,8 +583,9 @@ Keyboard.prototype.powerDown = function(fSave, fShutdown)
Keyboard.VT100.INIT = [
[
Keyboard.VT100.STATUS.INIT,
0 // iKeyNext
Keyboard.VT100.STATUS.INIT, // bVT100Status
Keyboard.VT100.ADDRESS.INIT, // bVT100Address
-1 // iKeyNext
]
];
@ -574,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 aKeysPressed array. Each 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
@ -582,7 +606,7 @@ Keyboard.prototype.reset = function()
*
* When the key is finally released (or auto-released), its entry is removed from the array.
*/
this.aKeysPressed = [];
this.aKeysActive = [];
if (this.config.INIT && !this.restore(this.config.INIT)) {
this.notice("reset error");
@ -604,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();
@ -630,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;
}
}
@ -717,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;
@ -728,13 +753,13 @@ Keyboard.prototype.onKeyDown = function(event, fDown)
*
* @this {Keyboard}
* @param {number|string} softCode
* @return {number} index of softCode in aKeysPressed, or -1 if not found
* @return {number} index of softCode in aKeysActive, or -1 if not found
*/
Keyboard.prototype.indexOfSoftKey = function(softCode)
{
var i;
for (i = 0; i < this.aKeysPressed.length; i++) {
if (this.aKeysPressed[i].softCode == softCode) return i;
for (i = 0; i < this.aKeysActive.length; i++) {
if (this.aKeysActive[i].softCode == softCode) return i;
}
return -1;
};
@ -753,30 +778,30 @@ Keyboard.prototype.onSoftKeyDown = function(softCode, fDown)
if (fDown) {
// this.println(softCode + " down");
if (i < 0) {
this.aKeysPressed.push({
this.aKeysActive.push({
softCode: softCode,
msDown: Date.now(),
fAutoRelease: false
});
} else {
this.aKeysPressed[i].msDown = Date.now();
this.aKeysPressed[i].fAutoRelease = false;
this.aKeysActive[i].msDown = Date.now();
this.aKeysActive[i].fAutoRelease = false;
}
} else if (i >= 0) {
// this.println(softCode + " up");
if (!this.aKeysPressed[i].fAutoRelease) {
var msDown = this.aKeysPressed[i].msDown;
if (!this.aKeysActive[i].fAutoRelease) {
var msDown = this.aKeysActive[i].msDown;
if (msDown) {
var msElapsed = Date.now() - msDown;
if (msElapsed < Keyboard.MINPRESSTIME) {
// this.println(softCode + " released after only " + msElapsed + "ms");
this.aKeysPressed[i].fAutoRelease = true;
this.aKeysActive[i].fAutoRelease = true;
this.checkSoftKeysToRelease();
return true;
}
}
}
this.aKeysPressed.splice(i, 1);
this.aKeysActive.splice(i, 1);
} else {
// this.println(softCode + " up with no down?");
}
@ -820,10 +845,10 @@ Keyboard.prototype.checkSoftKeysToRelease = function()
{
var i = 0;
var msDelayMin = -1;
while (i < this.aKeysPressed.length) {
if (this.aKeysPressed[i].fAutoRelease) {
var softCode = this.aKeysPressed[i].softCode;
var msDown = this.aKeysPressed[i].msDown;
while (i < this.aKeysActive.length) {
if (this.aKeysActive[i].fAutoRelease) {
var softCode = this.aKeysActive[i].softCode;
var msDown = this.aKeysActive[i].msDown;
var msElapsed = Date.now() - msDown;
var msDelay = Keyboard.MINPRESSTIME - msElapsed;
if (msDelay > 0) {
@ -849,9 +874,28 @@ Keyboard.prototype.checkSoftKeysToRelease = function()
}
};
/**
* checkBusy()
*
* Called whenever a ChipSet circuit needs the keyboard's UART status.
* Currently, we have no busy conditions (our virtual keyboard is infinitely fast).
*
* @this {Keyboard}
* @return {boolean}
*/
Keyboard.prototype.checkBusy = function()
{
return false;
};
/**
* 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)
@ -859,12 +903,24 @@ Keyboard.prototype.checkSoftKeysToRelease = function()
*/
Keyboard.prototype.inVT100UARTAddress = function(port, addrFrom)
{
var b = 0;
if (this.iKeyNext >= 0 && this.iKeyNext < this.aKeysPressed.length - 1) {
var softCode = this.aKeysPressed[this.iKeyNext++];
b = Keyboard.VT100.KEYMAP[softCode];
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.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;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long