Merge branch 'next-release'

This commit is contained in:
Jeff Parsons 2016-08-09 11:06:30 -07:00
commit 639cef608b
18 changed files with 6195 additions and 5240 deletions

View file

@ -17,7 +17,12 @@ indent_style = tab
indent_size = 8
trim_trailing_whitespace = true
[*.{C,H,c,h,xml,xsl}]
[*.{C,CPP,H,c,cpp,h}]
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.{xml,xsl}]
indent_style = tab
indent_size = 4
trim_trailing_whitespace = true

View file

@ -10,8 +10,7 @@ machines:
Space Invaders (1978)
---
This is a test of [PC8080](/modules/pc8080/), a new 8080-based machine emulator recently added to the
PCjs Project.
This is a test of [PC8080](/modules/pc8080/), a new 8080-based PCjs emulator running the original Space Invaders.
It is currently playable only in desktop browsers and uses the following hard-coded key mappings:

View file

@ -9,7 +9,8 @@
<rom id="romF" addr="0x1000" size="0x0800" file="/devices/pc8080/rom/invaders/INVADERS-F.json"/>
<rom id="romE" addr="0x1800" size="0x0800" file="/devices/pc8080/rom/invaders/INVADERS-E.json"/>
<ram id="ram" addr="0x2000" size="0x0400"/>
<video id="video" screenWidth="896" screenHeight="1024" bufferRotate="-90" smoothing="false" bufferAddr="0x2400" bufferCols="256" bufferRows="224" bufferBits="1" bufferLeft="0" interruptRate="120" width="40%" pos="left" padding="8px">
<video id="video" screenWidth="896" screenHeight="1024" bufferRotate="-90" smoothing="false" bufferFormat="si1978"
bufferAddr="0x2400" bufferCols="256" bufferRows="224" bufferBits="1" bufferLeft="0" interruptRate="120" width="40%" pos="left" padding="8px">
<menu>
<title>224x256 Screen (Rotated)</title>
<control type="container" pos="right">
@ -18,7 +19,7 @@
</menu>
</video>
<chipset id="chipset" model="SI1978"/>
<keyboard id="keyboard"/>
<keyboard id="keyboard" model="SI1978"/>
<panel ref="/devices/pc8080/panel/left.xml"/>
<debugger id="debugger" commands="s 8086"/>
</machine>

View file

@ -9,7 +9,8 @@
<rom id="romF" addr="0x1000" size="0x0800" file="/devices/pc8080/rom/invaders/INVADERS-F.json"/>
<rom id="romE" addr="0x1800" size="0x0800" file="/devices/pc8080/rom/invaders/INVADERS-E.json"/>
<ram id="ram" addr="0x2000" size="0x0400"/>
<video id="video" screenWidth="896" screenHeight="1024" bufferRotate="-90" smoothing="false" bufferAddr="0x2400" bufferCols="256" bufferRows="224" bufferBits="1" bufferLeft="0" interruptRate="120" padding="8px">
<video id="video" screenWidth="896" screenHeight="1024" bufferRotate="-90" smoothing="false" bufferFormat="si1978"
bufferAddr="0x2400" bufferCols="256" bufferRows="224" bufferBits="1" bufferLeft="0" interruptRate="120" padding="8px">
<menu>
<title>224x256 Screen (Rotated)</title>
<control type="container" pos="right">
@ -18,5 +19,5 @@
</menu>
</video>
<chipset id="chipset" model="SI1978"/>
<keyboard id="keyboard"/>
<keyboard id="keyboard" model="SI1978"/>
</machine>

View file

@ -34,11 +34,42 @@ See [DEC VT100 ROMs](/devices/pc8080/rom/vt100/) for more information about the
(aka [phooky](https://github.com/phooky) aka Adam Mayer) further explains VT100 memory usage:
Start End Size Description
0x0000 0x1fff 8K Basic ROM
0x2000 0x2012 18B Blank lines for refresh (6 x 3B)
0x2012 0x204f 61B Stack area (grows down from 0x204e)
0x204f 0x22d0 641B Scratch Pad/Setup Area(?)
0x22d0 0x2c00 2352B Screen RAM
0x0000 0x1fff 8K Basic ROM
0x2000 0x2012 18 Blank lines for refresh (6 x 3B)
0x2012 0x204f 61 Stack area (grows down from 0x204e)
0x204f 0x22d0 641 Scratch Pad/Setup Area(?)
0x22d0 0x2c00 2352 Screen RAM
Note: 0x22bb through 0x22d0 appear to be unused
In his [Platform Notes](https://github.com/phooky/VT100-Hax/blob/master/Platform%20Notes.md), he further describes
portions of the "Scratch Pad" area:
Start End Size Description
0x2052 0x2054 2 0x2004 during init?
0x2068 0x2069 1 Keys flag buffer
0x206a 0x206e 3 New keys pressed buffer
0x20f6 0x20f8 2 0x22d0 during init?
0x2014 0x2015 1 0xff during init? [Typo or reference to a byte in the Stack area? -JP]
and the "Setup" area:
Start End Size Description
0x217b 22 Answerback message (20chars+2delim)
0x2191 17 Tabs (bit encoding) (first bit always set)
0x21a2 1 80/132 col mode (00 = 80 col, 01 = 132 col)
0x21a3 1 intensity (00 = brightest, 0x1f = dimmest)
0x21a4 1 Mode byte for PUSART
0x21a5 1 Online/local
0x21a6 1 Switches 1
0x21a7 1 Switches 2
0x21a8 1 Switches 3
0x21a9 1 Switches 4
0x21aa 1 Switches 5
0x21ab 1 TX baud rate
0x21ac 1 RX baud rate
0x21ad 1 parity
0x21ae 1 nvr checksum
VT100 I/O Ports
---------------
@ -78,11 +109,11 @@ information about the Flags buffer (port 0x42):
Bit Active? Description
7 H KBD Transmit Buffer Empty
6 H LBA 7(?) (It's a pin on the backplane connector...) - used to clock NVR - line buffer address
6 H NVR CLOCK, driven by LBA7 (line buffer address)
5 H NVR DATA
4 L EVEN FIELD (comes out of the video timing generator)
3 H OPTION PRESENT (terminal output option???)
2 L GRAPHICS FLAG (is VT52 graphics card present) [I think he meant VT125 -JP]
2 L GRAPHICS FLAG (is VT125 graphics card present)
1 L ADVANCED VIDEO (is AVO present)
0 H XMIT FLAG
@ -127,6 +158,65 @@ measure 2.0mm x 3.35mm (in 132-column mode, they measure 1.3mm x 3.35mm), which
roughly 160mm x 80mm, implying a screen aspect ratio of 2.0. However, after visually comparing the Technical Manual's SET-UP
screenshots to our test screens, 1.67 appears to be closer to reality than 2.0. I'll revisit this issue at a later date.
VT100 Initialization Process
----------------------------
From "Power-Up and Self-Test", section 4.2.8, p. 4-19, of the VT100 Technical Manual (July 1982):
> When power is first applied to the terminal controller board, the reset circuit in the 8224 holds the microprocessor
in a halt state. Within a second, after the voltages stabilize in the power supply, the RC network at the reset input
allows tlhe input voltage to rise to the switching threshold of a Schmitt trigger. Then the reset is released with the
8080 program counter set to 0. The low 64 bytes of program are reserved for the eight interrupt service routines which
can be addressed by the restart instruction (see previous section). The low 8 start the power-up routine by disabling
the interrupts, setting up the stack pointer, and then going immediately into the self-test routines.
> Assuming there are no hard logic failures present on the board, the microprocessor attempts to perform a confidence
check of the controller. Some failures are considered fatal and will stop the machine; other failures limit its operation
but win not prevent its use. Fatal failures are indicated by the LEDs on the keyboard, while nonfatal errors are indicated
as a single character on the screen.
> The microprocessor first sends the number of the first ROM to the LEDs on the keyboard. Then it calculates a checksum
of the contents of the first 2K of program. (Since firmware is treated as four 2K blocks of code, later VT100s with one
8K X 8 ROM chip operate the same way but any block failure requires replacement of the one chip). At the time of ROM
preparation, a special byte was included within each block to make the checksum equal zero if there are no errors. If
there is an error, the microprocessor halts and the LEDs indicate the current ROM at the time of failure. Otherwise, the
LEDs are incremented to show the next ROM number and the process continues.
> The next part of the test is writing and reading the RAM. Every bit in the RAM is written with a 0 and a 1 and read each
time. If the advanced video option is present (as indicated by the Option Present flag), its RAM is tested immediately
after the main RAM. In the main RAM a failure halts the machine. Failure of a bit in the advanced video option RAM is
indicated on the screen and the process continues. In another termnnal, the VT52, one bad bit in the screen RAM means there
is one location that may not contain right character. This can be annoying to the user but does not affect the rest of the
screen. If one bit is bad in a VT100 line address, the entire screen below the affected line can become garbled and unsuable.
A bad bit in the scratch area could disable communication with the host. So this confidence check ensures that any RAM
failure is detected immediately.
> The next test checks the nonvola1tile RAM by reading it. A checksum is calculated and compared with the value stored the
last time the NVR was written during a save. A bad NVR does not stop the VT100 because the SET-UP values can always be
reestablished from the keyboard at power-up. The NVR test is also the normal time when the terminal gets its auto SET-UP
readings from the NVR. Time is saved because reading the NVR is the most time-consuming part of both the self-test and the
auto SET-UP. If the NVR fails, the bell sounds several times to inform the operator, and then default settings stored in
the ROM allow the terminal to work. The operator must then manually reset any parameters that differ from the default values.
> To test the keyboard, the microprocessor commands the keyboard to scan once, lights all the LEDs, for about a half second,
and sounds the bell. It waits for the scan to finish and then looks for the last key address 7FH at the keyboard UART.
If the test fails, the terminal remains on-line, making it a receive-only (RO) terminal.
> This is the end of testing.
> Once the NVR data is in the scratch area in RAM, the microprocessor uses that data to program the hardware. All operating
parameters that were last saved (see NVR) are recalled and the terminal is set to match them. Finally the cursor appears
at column 1, line 1, and the microprocessor enters its background routine, ready for operation.
Some additional observations:
- During the NVR test, a "WAIT" message is displayed in the top-left corner of the screen.
- Following the NVR test, code at 0x00D2 loops for 0xFFF (4095) times with the CLICK bit (0x80) set in the keyboard STATUS
port (0x82), generating a "bell" (beep), presumably because the NVR test failed. And one would expect the NVR checksum test
to fail, since we initialize all NVR words with the freshly-erased value of 0x3fff. Also, at some point before arriving at the
preceding loop, the "WAIT" message has been cleared.
Additional VT100 Resources
--------------------------

View file

@ -6,7 +6,7 @@
<cpu id="cpu8080" model="8080" cycles="2764800"/>
<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"
<video id="video" screenWidth="1600" screenHeight="960" smoothing="false" interruptRate="60"
fontROM="/devices/pc8080/rom/vt100/23-018E2.json" fontColor="white" cellWidth="10" cellHeight="10"
bufferAddr="0x2000" bufferRAM="true" bufferFormat="vt100" bufferCols="80" bufferRows="24" pos="left" padding="8px">
<menu>
@ -24,7 +24,8 @@
</menu>
</video>
<chipset id="chipset" model="VT100"/>
<keyboard id="keyboard"/>
<keyboard id="keyboard" model="VT100"/>
<serial id="serial0" adapter="0"/>
<panel ref="/devices/pc8080/panel/wide.xml"/>
<debugger id="debugger" messages="mem" commands="s 8086"/>
<debugger id="debugger" messages="mem|port" commands="s 8086"/>
</machine>

View file

@ -6,7 +6,7 @@
<cpu id="cpu8080" model="8080" cycles="2764800"/>
<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"
<video id="video" screenWidth="1600" screenHeight="960" smoothing="false" interruptRate="60"
fontROM="/devices/pc8080/rom/vt100/23-018E2.json" fontColor="white" cellWidth="10" cellHeight="10"
bufferAddr="0x2000" bufferRAM="true" bufferFormat="vt100" bufferCols="80" bufferRows="24" padding="8px">
<menu>
@ -24,5 +24,5 @@
</menu>
</video>
<chipset id="chipset" model="VT100"/>
<keyboard id="keyboard"/>
<keyboard id="keyboard" model="VT100"/>
</machine>

View file

@ -15,8 +15,9 @@ The combined contents of those ROMs have been stored as an 8Kb JSON image in [VT
As Trammell Hudson's [VT100 Page](https://trmm.net/VT100) explains:
There are four 8316E ROM chips for the 8080 CPU on the logic board. Since they are custom mask ROMs,
Digital was able to have a different chip-select bit pattern and avoided having separate NOT gates on the inputs.
Note that the schematic appears to be incorrect -- E56 is the low order, and E40 is the high order address bits.
Digital was able to have a different chip-select bit pattern and avoided having separate NOT gates on
the inputs. Note that the schematic appears to be incorrect -- E56 is the low order, and E40 is the
high order address bits.
He then describes the memory map as follows:
@ -26,15 +27,18 @@ He then describes the memory map as follows:
E52 H H L 0x0800
E56 H L L 0x0000
The above PCB chip locations correspond to the following [DEC ROM](/devices/roms/dec/) dumps:
The above PCB chip locations correspond to the following [DEC ROMs](/devices/roms/dec/):
* E56: [23-061E2.bin](https://web.archive.org/web/20140723115846/http://www.dunnington.u-net.com/public/DECROMs/23-061E2.bin)
* E52: [23-032E2.bin](https://web.archive.org/web/20140723115846/http://www.dunnington.u-net.com/public/DECROMs/23-032E2.bin)
* E45: [23-033E2.bin](https://web.archive.org/web/20140723115846/http://www.dunnington.u-net.com/public/DECROMs/23-033E2.bin)
* E40: [23-034E2.bin](https://web.archive.org/web/20140723115846/http://www.dunnington.u-net.com/public/DECROMs/23-034E2.bin)
And sure enough, concatenating those four DEC ROM dumps produced a perfect match for Trammell Hudson's
[VT100.bin](http://trmm.net/images/2/20/VT100.bin).
And sure enough, concatenating those four DEC ROMs:
cat 23-061E2.bin 23-032E2.bin 23-033E2.bin 23-034E2.bin > VT100.bin
produces a perfect match for Trammell Hudson's [VT100.bin](http://trmm.net/images/2/20/VT100.bin).
### Character Generator (2Kb)
@ -46,7 +50,7 @@ The ROM contains 128 rows of character data, 16 bytes per character. More on th
Following in the footsteps of [vt100romhax](http://vt100romhax.tumblr.com/post/90697428973/the-vt100-memory-map-and-8080-disassembly),
I disassembled the ROM, using `dz80` from [D52](http://www.brouhaha.com/~eric/software/d52/) ([manual](http://www.bipom.com/documents/dis51/d52manual.html)):
dz80 -80 archive/VT100.bin
dz80 -80 VT100.bin
This produced VT100.d80, which I renamed to [VT100.asm](VT100.asm). I fixed one `dz80` bug, replacing references to
`X2000` with `2000h`, and then hand-merged most of the comments from [haxrom.d80](https://github.com/phooky/VT100-Hax/blob/master/ROMs/haxrom.d80).
@ -64,8 +68,8 @@ and passed on to the PC8080 Debugger. Here are the rebuild steps:
grep -E "[0-9]+ [0-9A-D]+.*;;" VT100.lst | sed -E "s/ *[0-9]+ ([0-9A-F]+).*;(;.*)/ \1 . \2/" > VT100.map
filedump --file=VT100.bin --format=bytes --output=VT100.json --comments --overwrite
You can omit `--comments` to reduce the size of the [VT100.json](VT100.json) file.
You can omit `--comments` from the filedump command to reduce the size of the [VT100.json](VT100.json) file.
Some [VT100.asm](VT100.asm) clean-up remains, because there are still chunks of data that were incorrectly disassembled as code.
From a reassembly standpoint, it doesn't matter too much, because such instructions get reassembled into the same original binary
patterns, but from a readability standpoint, it's a nuisance.
From a reassembly standpoint, it doesn't matter too much, because all the 8080 instructions unambiguously reassemble into their
original binary patterns, but from a readability standpoint, it's a nuisance.

View file

@ -14,7 +14,9 @@ X0000: di
jmp X003b
org 8
;
; Interrupt vector 0x1 for keyboard
;
X0008: call X00fd
ei
ret
@ -160,6 +162,9 @@ X00c4: jz X00cb
X00cb: mvi a,2fh
sta X21c9
out 62h
;
; Prepare to enter the "beep" loop
;
X00d2: lxi b,X0fff
ei
X00d6: mvi a,8
@ -172,7 +177,10 @@ X00e0: out 82h
mov a,b
ora c
jnz X00d6
out 82h
;
; Done "beeping" now
;
X00e8: out 82h ; A = 0, so all LEDs off now
lda X2068
ora a
jm X00f5
@ -180,12 +188,43 @@ X00e0: out 82h
ori 4
mov d,a
X00f5: push d
;
; The next call issues the following:
;
; 03A2 CD8103 CALL 0381 ;stack=204A
; 0B67 CDAD10 CALL 10AD ;stack=2048
; chipset.outPort(0x00A2,DC012,0x00) at 10BD
; chipset.outPort(0x00A2,DC012,0x04) at 10C1
;
; and then:
;
; 03A2 CD8103 CALL 0381 ;stack=204A
; 0B67 CDAD10 CALL 10AD ;stack=2048
; 10C9 CDAA0F CALL 0FAA ;stack=2046
; 0FB6 CC8814 CALLZ 1488 ;stack=2040
; 1488 CD9314 CALL 1493 ;stack=203E
; chipset.inPort(0x0042,FLAGS.BUFFER): 0x60 at 1493
;
; Note that once we're inside 0FAA, that appears to be where we stay.
;
call X03a2
pop d
jmp X0875
;;
;; Keyboard interrupt handler
;;
;
; Keyboard interrupt handler
;
; The "Keys Flag Byte/Buffer" at 0x2068 contains the following bits:
;
; 0-1 Key counter
; 2 Key counter overflow bit
; 3 Setup
; 4 Control
; 5 Shift
; 6 Caps Lock
; 7 Last Key
;
X00fd: push psw
in 82h ;; Read from keyboard port
push h
@ -934,10 +973,10 @@ X06a7: mov a,d
ora a
ret
X06aa: lda X2068
X06aa: lda X2068 ; A <- [Keys flag buffer]
mov e,a
ani 80h
rz
ani 80h ; bit 7 set?
rz ; return if not
lxi h,X0841
push h
mov a,e
@ -2344,6 +2383,22 @@ X0f9e: ora m
mov m,a
ret
;
; Main loop?
;
; Absent any interrupts, execution gravitates here:
;
; 0FAA:
; ...
; call 1488
; call 1493 ; read port 0x42 (check flags buffer)
; call 06aa ; check byte at [2068]
; cli
; call 0e3e ; fetch byte at [21A5], OR byte at [207B]
; sti
; ; return byte at [2172] in A
; jmp 0FAE
;
X0faa: lxi h,X207f
add m
X0fae: cmp m
@ -2356,6 +2411,9 @@ X0fb2: lda X207b
pop psw
pop h
jmp X0fae
;
; End of main loop?
;
X0fbe: lhld X204e
mov a,h
@ -4728,46 +4786,7 @@ X1fd4: ora a
; Last but not least, pad out to 8K (8192) bytes
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
org 1fffh
nop
; Miscellaneous equates
@ -4797,7 +4816,7 @@ X205b equ 205bh
X205c equ 205ch
X2065 equ 2065h
X2067 equ 2067h
X2068 equ 2068h
X2068 equ 2068h ; Keys flag buffer
X2069 equ 2069h
X206a equ 206ah
X206e equ 206eh

File diff suppressed because it is too large Load diff

View file

@ -59,16 +59,11 @@ function ChipSet(parmsChipSet)
var model = parmsChipSet['model'];
/*
* this.model is a numeric version of the 'model' string; when comparing this.model to "base"
* model numbers, you should generally compare (this.model|0) to the target value, which truncates it.
*/
if (model && !ChipSet.MODELS[model]) {
Component.notice("Unrecognized ChipSet model: " + model);
}
this.config = ChipSet.MODELS[model] || ChipSet.SI1978;
this.model = this.config.MODEL;
this.config = ChipSet.MODELS[model] || {};
this.bSwitches = this.parseDIPSwitches(parmsChipSet['swDIP']);
@ -105,6 +100,12 @@ function ChipSet(parmsChipSet)
Component.subclass(ChipSet);
/*
* NOTE: The STATUS1 port could have been handled entirely by the Keyboard component, but it was just as easy
* to create a simple ChipSet interface, updateStatus1(), that the Keyboard calls whenever it wants to simulate a
* button press or release. It's a six-of-one, half-a-dozen-of-another choice, since technically, Space Invaders
* doesn't have a keyboard.
*/
ChipSet.SI1978 = {
MODEL: 1978.1,
STATUS0: { // NOTE: STATUS0 not used by the SI1978 ROMs; refer to STATUS1 instead
@ -173,14 +174,39 @@ ChipSet.SI1978 = {
*
* <cpu id="cpu8080" model="8080" cycles="2764800"/>
*
* where 2764800 = 24883200 / 9. Beyond that, we don't really care about that particular 8224. I only mention it
* because knowing the CPU frequency is helpful for simulating some of the other circuits below that we DO care about.
* where 2764800 = 24883200 / 9. You need to know this because we rely on the CPU frequency for simulating some
* of the other VT100 circuits.
*
* 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):
*
* READ OR WRITE
* 00H PUSART data bus
* 01H PUSART command port
*
* WRITE ONLY (Decoded with I/O WR L)
* 02H Baud rate generator
* 42H Brightness D/A latch
* 62H NVR latch
* 82H Keyboard UART data input [used to update the Keyboard Status Byte -JP]
* A2H Video processor DC012
* C2H Video processor DC011
* E2H Graphics port
*
* READ ONLY (Decoded with I/O RD L)
* 22H Modem buffer
* 42H Flags buffer
* 82H Keyboard UART data output
*
* Most of these are handled by the ChipSet component, since it exists as sort of a "catch-all" component,
* but some are more appropriately handled by other components; eg, port 0x82 is handled by the Keyboard component,
* so it's defined there instead of here.
*/
ChipSet.VT100 = {
MODEL: 100.0,
FLAGS_BUFFER: {
PORT: 0x42, // read-only
XMIT: 0x01, // active if SET
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
@ -207,13 +233,65 @@ ChipSet.VT100 = {
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."
*/
},
/*
* DC012 is referred to as a Control Chip.
*
* As p. 4-67 (117) of the VT100 Technical Manual (July 1982) explains:
*
* The DCO12 performs three main functions.
*
* 1. Scan count generation. This involves two counters, a multiplexer to switch between the counters,
* double-height logic, scroll and line attribute latches, and various logic controlling switching between
* the two counters. This is the biggest part of the chip. It includes all scrolling, double-height logic,
* and feeds into the underline and hold request circuits.
*
* 2. Generation of HOLD REQUEST. This uses information from the scan counters and the scrolling logic to
* decide when to generate HOLD REQUEST.
*
* 3. Video modifications: dot stretching, blanking, addition of attributes to video outputs, and multiple
* intensity levels.
*
* The input decoder accepts a 4-bit command from the microprocessor when VID WR 2 L is asserted. Table 4-6-2
* lists the commands.
*
* D3 D2 D1 D0 Function
* -- -- -- -- --------
* 0 0 0 0 Load low order scroll latch = 00
* 0 0 0 1 Load low order scroll latch = 01
* 0 0 1 0 Load low order scroll latch = 10
* 0 0 1 1 Load low order scroll latch = 11
*
* 0 1 0 0 Load high order scroll latch = 00
* 0 1 0 1 Load high order scroll latch = 01
* 0 1 1 0 Load high order scroll latch = 10
* 0 1 1 1 Load high order scroll latch = 11 (not used)
*
* 1 0 0 0 Toggle blink flip-flop
* 1 0 0 1 Clear vertical frequency interrupt
*
* 1 0 1 0 Set reverse field on
* 1 0 1 1 Set reverse field off
*
* 1 1 0 0 Set basic attribute to underline*
* 1 1 0 1 Set basic attribute to reverse video*
* 1 1 1 0 Reserved for future specification*
* 1 1 1 1 Reserved for future specification*
*
* *These functions also clear blink flip-flop.
*/
DC012: { // generates scan counts for the Video Processor
PORT: 0xA2, // write-only
INIT: 0x00 // for lack of a better guess
},
/*
* As p. 4-55 (105) of the July 1982 Technical Manual explains:
* 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
@ -246,7 +324,7 @@ ChipSet.VT100 = {
};
/*
* Supported model strings
* Supported models and their configurations
*/
ChipSet.MODELS = {
"SI1978": ChipSet.SI1978,
@ -345,7 +423,7 @@ ChipSet.prototype.powerDown = function(fSave, fShutdown)
return fSave? this.save() : true;
};
ChipSet.SI1978.init = [
ChipSet.SI1978.INIT = [
[
ChipSet.SI1978.STATUS0.ALWAYS_SET,
ChipSet.SI1978.STATUS1.ALWAYS_SET,
@ -354,7 +432,7 @@ ChipSet.SI1978.init = [
]
];
ChipSet.VT100.init = [
ChipSet.VT100.INIT = [
[
ChipSet.VT100.BRIGHTNESS.INIT,
ChipSet.VT100.FLAGS_BUFFER.NO_AVO | ChipSet.VT100.FLAGS_BUFFER.NO_GFX,
@ -362,7 +440,19 @@ ChipSet.VT100.init = [
ChipSet.VT100.DC011.INIT
],
[
0, 0, 0, 0, new Array(100)
0, 0, 0, 0,
[
0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80,
0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80,
0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80,
0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e80, 0x2e00,
0x2e08, 0x2e8e, 0x2e00, 0x2e50, 0x2e30, 0x2e40, 0x2e20, 0x2e00, 0x2ee0, 0x2ee0,
0x2e51, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
]
]
];
@ -373,7 +463,7 @@ ChipSet.VT100.init = [
*/
ChipSet.prototype.reset = function()
{
if (!this.restore(this.config.init)) {
if (this.config.INIT && !this.restore(this.config.INIT)) {
this.notice("reset error");
}
};
@ -389,7 +479,7 @@ ChipSet.prototype.reset = function()
ChipSet.prototype.save = function()
{
var state = new State(this);
switch(this.model) {
switch(this.config.MODEL) {
case ChipSet.SI1978.MODEL:
state.set(0, [this.bStatus0, this.bStatus1, this.bStatus2, this.wShiftData, this.bShiftCount, this.bSound1, this.bSound2]);
break;
@ -414,7 +504,7 @@ ChipSet.prototype.restore = function(data)
{
var a;
if (data && (a = data[0]) && a.length) {
switch(this.model) {
switch(this.config.MODEL) {
case ChipSet.SI1978.MODEL:
this.bStatus0 = a[0];
this.bStatus1 = a[1];
@ -426,9 +516,9 @@ ChipSet.prototype.restore = function(data)
return true;
case ChipSet.VT100.MODEL:
this.bBrightness = a[0];
this.bFlagsBuffer = a[2];
this.bDC012 = a[3];
this.bDC011 = a[4];
this.bFlagsBuffer = a[1];
this.bDC012 = a[2];
this.bDC011 = a[3];
a = data[1];
this.dNVRAddr = a[0]; // 20-bit address
this.wNVRData = a[1]; // 14-bit word
@ -716,7 +806,7 @@ ChipSet.prototype.doNVRCommand = function()
addr = this.getNVRAddr();
data = this.aNVRWords[addr];
/*
* Since we don't explicitly initialize aNVRWords[], we pretend any uninitialized words contains WORDMASK.
* If we don't explicitly initialize aNVRWords[], pretend any uninitialized words contains WORDMASK.
*/
if (data == null) data = ChipSet.VT100.NVR.WORDMASK;
this.wNVRData = data;
@ -763,7 +853,7 @@ ChipSet.prototype.inVT100FlagsBuffer = function(port, addrFrom)
b |= ChipSet.VT100.FLAGS_BUFFER.NVR_DATA;
}
this.bFlagsBuffer = b;
this.printMessageIO(port, null, addrFrom, "FLAGS.BUFFER", b, true);
this.printMessageIO(port, null, addrFrom, "FLAGS.BUFFER", b);
return b;
};
@ -777,7 +867,7 @@ ChipSet.prototype.inVT100FlagsBuffer = function(port, addrFrom)
*/
ChipSet.prototype.outVT100Brightness = function(port, b, addrFrom)
{
this.printMessageIO(port, b, addrFrom, "BRIGHTNESS", null, true);
this.printMessageIO(port, b, addrFrom, "BRIGHTNESS");
this.bBrightness = b;
};
@ -791,7 +881,7 @@ ChipSet.prototype.outVT100Brightness = function(port, b, addrFrom)
*/
ChipSet.prototype.outVT100NVRLatch = function(port, b, addrFrom)
{
this.printMessageIO(port, b, addrFrom, "NVR.LATCH", null, true);
this.printMessageIO(port, b, addrFrom, "NVR.LATCH");
this.bNVRLatch = b;
};
@ -805,7 +895,7 @@ ChipSet.prototype.outVT100NVRLatch = function(port, b, addrFrom)
*/
ChipSet.prototype.outVT100DC012 = function(port, b, addrFrom)
{
this.printMessageIO(port, b, addrFrom, "DC012", null, true);
this.printMessageIO(port, b, addrFrom, "DC012");
this.bDC012 = b;
};
@ -819,7 +909,7 @@ ChipSet.prototype.outVT100DC012 = function(port, b, addrFrom)
*/
ChipSet.prototype.outVT100DC011 = function(port, b, addrFrom)
{
this.printMessageIO(port, b, addrFrom, "DC011", null, true);
this.printMessageIO(port, b, addrFrom, "DC011");
this.bDC011 = b;
};

View file

@ -42,6 +42,10 @@ if (NODE) {
/**
* Keyboard(parmsKbd)
*
* The Keyboard component has the following component-specific (parmsKbd) properties:
*
* model: eg, "VT100" (should be a member of Keyboard.MODELS)
*
* @constructor
* @extends Component
* @param {Object} parmsKbd
@ -50,6 +54,14 @@ function Keyboard(parmsKbd)
{
Component.call(this, "Keyboard", parmsKbd, Keyboard, Messages.KEYBOARD);
var model = parmsKbd['model'];
if (model && !Keyboard.MODELS[model]) {
Component.notice("Unrecognized Keyboard model: " + model);
}
this.config = Keyboard.MODELS[model] || {};
this.reset();
this.setReady();
@ -57,20 +69,6 @@ function Keyboard(parmsKbd)
Component.subclass(Keyboard);
Keyboard.VT100 = {
STATUS: {
PORT: 0x82, // write-only
LED4: 0x01,
LED3: 0x02,
LED2: 0x04,
LED1: 0x08,
LOCKED: 0x10,
ONLINE: 0x20,
START: 0x40,
CLICK: 0x80
}
};
/**
* Alphanumeric and other common (printable) ASCII codes.
*
@ -104,8 +102,7 @@ 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). This conflict is
* discussed further in the definition of CLICKCODE below.
* them using ASCII codes (eg, the LEFT arrow key uses the ASCII code for '%' or 37).
*
* @enum {number}
*/
@ -118,7 +115,7 @@ Keyboard.KEYCODE = {
/* 0x11 */ CTRL: 17,
/* 0x12 */ ALT: 18,
/* 0x13 */ PAUSE: 19, // PAUSE/BREAK
/* 0x14 */ CAPS_LOCK: 20,
/* 0x14 */ CAPSLOCK: 20,
/* 0x1B */ ESC: 27,
/* 0x20 */ SPACE: 32,
/* 0x21 */ PGUP: 33,
@ -153,21 +150,31 @@ Keyboard.KEYCODE = {
/* 0x5C */ FF_BSLASH: 92,
/* 0x5D */ RCMD: 93, // aka MENU
/* 0x5D */ FF_RBRACK: 93,
/* 0x60 */ NUM_INS: 96, // 0
/* 0x60 */ NUM_0: 96,
/* 0x60 */ NUM_INS: 96,
/* 0x60 */ FF_BQUOTE: 96,
/* 0x61 */ NUM_END: 97, // 1
/* 0x62 */ NUM_DOWN: 98, // 2
/* 0x63 */ NUM_PGDN: 99, // 3
/* 0x64 */ NUM_LEFT: 100, // 4
/* 0x65 */ NUM_CENTER: 101, // 5
/* 0x66 */ NUM_RIGHT: 102, // 6
/* 0x67 */ NUM_HOME: 103, // 7
/* 0x68 */ NUM_UP: 104, // 8
/* 0x69 */ NUM_PGUP: 105, // 9
/* 0x61 */ NUM_1: 97,
/* 0x61 */ NUM_END: 97,
/* 0x62 */ NUM_2: 98,
/* 0x62 */ NUM_DOWN: 98,
/* 0x63 */ NUM_3: 99,
/* 0x63 */ NUM_PGDN: 99,
/* 0x64 */ NUM_4: 100,
/* 0x64 */ NUM_LEFT: 100,
/* 0x65 */ NUM_5: 101,
/* 0x65 */ NUM_CENTER: 101,
/* 0x66 */ NUM_6: 102,
/* 0x66 */ NUM_RIGHT: 102,
/* 0x67 */ NUM_7: 103,
/* 0x67 */ NUM_HOME: 103,
/* 0x68 */ NUM_8: 104,
/* 0x68 */ NUM_UP: 104,
/* 0x69 */ NUM_9: 105,
/* 0x69 */ NUM_PGUP: 105,
/* 0x6A */ NUM_MUL: 106,
/* 0x6B */ NUM_ADD: 107,
/* 0x6D */ NUM_SUB: 109,
/* 0x6E */ NUM_DEL: 110, // .
/* 0x6E */ NUM_DEL: 110, // aka PERIOD
/* 0x6F */ NUM_DIV: 111,
/* 0x70 */ F1: 112,
/* 0x71 */ F2: 113,
@ -217,6 +224,11 @@ Keyboard.KEYCODE = {
FAKE: 4000
};
/*
* Check the event object's 'location' property for a non-zero value for the following ONRIGHT keys.
*/
Keyboard.KEYCODE.NUM_CR = Keyboard.KEYCODE.CR + Keyboard.KEYCODE.ONRIGHT;
/*
* Maps "stupid" keyCodes to their "non-stupid" counterparts
*/
@ -234,20 +246,6 @@ Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.RBRACK] = Keyboard.ASCII[']']; // 2
Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.QUOTE] = Keyboard.ASCII["'"]; // 222 -> 39
Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.FF_DASH] = Keyboard.ASCII['-'];
/**
* Maps SOFTCODE (string) to KEYCODE (number).
*
* @enum {number}
*/
Keyboard.SOFTCODES = {
'1p': Keyboard.KEYCODE.ONE,
'2p': Keyboard.KEYCODE.TWO,
'coin': Keyboard.KEYCODE.THREE,
'left': Keyboard.KEYCODE.LEFT,
'right': Keyboard.KEYCODE.RIGHT,
'fire': Keyboard.KEYCODE.SPACE
};
Keyboard.MINPRESSTIME = 100; // 100ms
/**
@ -261,7 +259,148 @@ Keyboard.ALTCODES[Keyboard.ASCII.A] = Keyboard.KEYCODE.LEFT;
Keyboard.ALTCODES[Keyboard.ASCII.D] = Keyboard.KEYCODE.RIGHT;
Keyboard.ALTCODES[Keyboard.ASCII.L] = Keyboard.KEYCODE.SPACE;
Keyboard.LEDSTATES = {
/*
* Supported configurations
*/
Keyboard.SI1978 = {
MODEL: 1978.1,
SOFTCODES: {
'1p': Keyboard.KEYCODE.ONE,
'2p': Keyboard.KEYCODE.TWO,
'coin': Keyboard.KEYCODE.THREE,
'left': Keyboard.KEYCODE.LEFT,
'right': Keyboard.KEYCODE.RIGHT,
'fire': Keyboard.KEYCODE.SPACE
}
};
Keyboard.VT100 = {
MODEL: 100.0,
SOFTCODES: {},
/*
* 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 aKeysPressed array, along with a final interrupt for KEYLAST.
*/
ADDRESS: {
PORT: 0x82
},
/*
* Writing port 0x82 updates the VT100's keyboard status byte via the keyboard's UART data input.
*/
STATUS: {
PORT: 0x82, // write-only
LED4: 0x01,
LED3: 0x02,
LED2: 0x04,
LED1: 0x08,
LOCKED: 0x10,
ONLINE: 0x20,
LEDS: 0x3F, // all LEDs
START: 0x40, // set to initiate a scan
/*
* From p. 4-38 of the VT100 Technical Manual (July 1982):
*
* A bit (CLICK) in the keyboard status word controls the bell.... When a single status word contains
* the bell bit, flip-flop E3 toggles and turns on E1, generating a click. If the bell bit is set for
* many words in succession, the UART latch holds the data output constant..., allowing the circuit to
* produce an 800 hertz tone. Bell is generated by setting the bell bit for 0.25 seconds. Each cycle of
* the tone is at a reduced amplitude compared with the single keyclick.... The overall effect of the
* tone burst on the ear is that of a beep.
*/
CLICK: 0x80,
INIT: 0x00
},
KEYLAST: 0x7F
};
Keyboard.VT100.KEYMAP = {};
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.DEL] = 0x03;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.P] = 0x05;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.O] = 0x06;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.Y] = 0x07;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.T] = 0x08;
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['[']] = 0x15;
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.KEYCODE.LEFT] = 0x20;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.DOWN] = 0x22;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.PAUSE] = 0x23; // aka BREAK
Keyboard.VT100.KEYMAP[Keyboard.ASCII['`']] = 0x24;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['-']] = 0x25;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['9']] = 0x26;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['7']] = 0x27;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['4']] = 0x28;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['3']] = 0x29;
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['0']] = 0x35;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['8']] = 0x36;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['6']] = 0x37;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['5']] = 0x38;
Keyboard.VT100.KEYMAP[Keyboard.ASCII['2']] = 0x39;
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.ASCII['\\']] = 0x45;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.L] = 0x46;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.K] = 0x47;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.G] = 0x48;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.F] = 0x49;
Keyboard.VT100.KEYMAP[Keyboard.ASCII.A] = 0x4A;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.NUM_8] = 0x50;
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[';']] = 0x56;
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.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[',']] = 0x66;
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.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.ASCII['/']] = 0x75;
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.CTRL] = 0x7C;
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.SHIFT] = 0x7D; // either shift key (doesn't matter)
Keyboard.VT100.KEYMAP[Keyboard.KEYCODE.CAPSLOCK]= 0x7E;
Keyboard.VT100.LEDCODES = {
'l4': Keyboard.VT100.STATUS.LED4,
'l3': Keyboard.VT100.STATUS.LED3,
'l2': Keyboard.VT100.STATUS.LED2,
@ -271,6 +410,14 @@ Keyboard.LEDSTATES = {
'local': ~Keyboard.VT100.STATUS.ONLINE
};
/*
* Supported models and their configurations
*/
Keyboard.MODELS = {
"SI1978": Keyboard.SI1978,
"VT100": Keyboard.VT100
};
/**
* setBinding(sHTMLType, sBinding, control, sValue)
*
@ -306,7 +453,7 @@ Keyboard.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
if (this.bindings[id] === undefined) {
if (sHTMLType == "led" && Keyboard.LEDSTATES[sBinding]) {
if (sHTMLType == "led" && this.config.LEDCODES && this.config.LEDCODES[sBinding]) {
this.bindings[id] = control;
return true;
}
@ -330,7 +477,7 @@ Keyboard.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
return true;
default:
if (Keyboard.SOFTCODES[sBinding] !== undefined) {
if (this.config.SOFTCODES && this.config.SOFTCODES[sBinding] !== undefined) {
this.bindings[id] = control;
var fnDown = function(kbd, sSoftCode) {
return function onMouseOrTouchDownKeyboard(event) {
@ -368,18 +515,11 @@ Keyboard.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
*/
Keyboard.prototype.initBus = function(cmp, bus, cpu, dbg)
{
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.model = this.chipset.model;
switch(this.model) {
case ChipSet.VT100.MODEL:
this.config = Keyboard.VT100;
break;
}
if (this.config) {
bus.addPortInputTable(this, this.config.portsInput);
bus.addPortOutputTable(this, this.config.portsOutput);
}
bus.addPortInputTable(this, this.config.portsInput);
bus.addPortOutputTable(this, this.config.portsOutput);
};
/**
@ -415,9 +555,10 @@ Keyboard.prototype.powerDown = function(fSave, fShutdown)
return fSave? this.save() : true;
};
Keyboard.VT100.init = [
Keyboard.VT100.INIT = [
[
0
Keyboard.VT100.STATUS.INIT,
0 // iKeyNext
]
];
@ -429,16 +570,18 @@ Keyboard.VT100.init = [
Keyboard.prototype.reset = function()
{
/*
* As keyDown events are encountered, a corresponding "softcode" property in keysPressed
* is set to the timestamp of the keyDown event. When the corresponding keyUp event occurs,
* we look at the elapsed time: if it is less than MINPRESSTIME, then we move the key
* to keysToRelease and set a timeout handler to release the key later; otherwise, we process
* the keyUp event immediately.
* 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:
*
* softCode: number or string representing the key pressed
* msDown: timestamp of the most recent "down" event
* fAutoRelease: true to auto-release the key after MINPRESSTIME (set when "up" occurs too quickly)
*
* When the key is finally released (or auto-released), its entry is removed from the array.
*/
this.keysPressed = {};
this.keysToRelease = {};
this.aKeysPressed = [];
if (this.config && !this.restore(this.config.init)) {
if (this.config.INIT && !this.restore(this.config.INIT)) {
this.notice("reset error");
}
};
@ -454,11 +597,11 @@ Keyboard.prototype.reset = function()
Keyboard.prototype.save = function()
{
var state = new State(this);
switch(this.model) {
case ChipSet.SI1978.MODEL:
switch(this.config.MODEL) {
case Keyboard.SI1978.MODEL:
break;
case ChipSet.VT100.MODEL:
state.set(0, [this.bLEDs]);
case Keyboard.VT100.MODEL:
state.set(0, [this.bVT100Status]);
break;
}
return state.data();
@ -477,12 +620,14 @@ Keyboard.prototype.restore = function(data)
{
var a;
if (data && (a = data[0]) && a.length) {
switch(this.model) {
case ChipSet.SI1978.MODEL:
switch(this.config.MODEL) {
case Keyboard.SI1978.MODEL:
return true;
case ChipSet.VT100.MODEL:
this.bLEDs = a[0];
this.updateLEDs();
case Keyboard.VT100.MODEL:
this.bVT100Status = a[0];
this.updateLEDs(this.bVT100Status & Keyboard.VT100.STATUS.LEDS);
this.iKeyNext = a[1];
return true;
}
}
@ -505,20 +650,22 @@ Keyboard.prototype.setLED = function(control, f)
};
/**
* updateLEDs()
* updateLEDs(bLEDs)
*
* @this {Keyboard}
* @param {number} bLEDs
*/
Keyboard.prototype.updateLEDs = function()
Keyboard.prototype.updateLEDs = function(bLEDs)
{
for (var sBinding in Keyboard.LEDSTATES) {
this.bLEDs = bLEDs;
for (var sBinding in this.config.LEDCODES) {
var id = "led-" + sBinding;
var control = this.bindings[id];
if (control) {
var bitLED = Keyboard.LEDSTATES[sBinding];
var fOn = !!(this.bLEDs & bitLED);
var bitLED = this.config.LEDCODES[sBinding];
var fOn = !!(bLEDs & bitLED);
if (bitLED & (bitLED-1)) {
fOn = !(this.bLEDs & ~bitLED);
fOn = !(bLEDs & ~bitLED);
}
this.setLED(control, fOn);
}
@ -528,14 +675,19 @@ Keyboard.prototype.updateLEDs = function()
/**
* getSoftCode(keyCode)
*
* Returns a number if the keyCode exists in the KEYMAP, or a string if the keyCode has a soft-code string.
*
* @this {Keyboard}
* @return {string|null}
* @return {string|number|null}
*/
Keyboard.prototype.getSoftCode = function(keyCode)
{
keyCode = Keyboard.ALTCODES[keyCode] || keyCode;
for (var sSoftCode in Keyboard.SOFTCODES) {
if (Keyboard.SOFTCODES[sSoftCode] === keyCode) {
if (this.config.KEYMAP[keyCode]) {
return keyCode;
}
for (var sSoftCode in this.config.SOFTCODES) {
if (this.config.SOFTCODES[sSoftCode] === keyCode) {
return sSoftCode;
}
}
@ -554,10 +706,10 @@ Keyboard.prototype.onKeyDown = function(event, fDown)
{
var fPass = true;
var keyCode = event.keyCode;
var sSoftCode = this.getSoftCode(keyCode);
var softCode = this.getSoftCode(keyCode);
if (sSoftCode) {
fPass = this.onSoftKeyDown(sSoftCode, fDown);
if (softCode) {
fPass = this.onSoftKeyDown(softCode, fDown);
event.preventDefault();
}
@ -569,36 +721,65 @@ Keyboard.prototype.onKeyDown = function(event, fDown)
};
/**
* onSoftKeyDown(sSoftCode, fDown)
* indexOfSoftKey(softCode)
*
* @this {Keyboard}
* @param {string} sSoftCode
* @param {number|string} softCode
* @return {number} index of softCode in aKeysPressed, 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;
}
return -1;
};
/**
* onSoftKeyDown(softCode, fDown)
*
* @this {Keyboard}
* @param {number|string} softCode
* @param {boolean} fDown is true for a down event, false for up
* @return {boolean} true to pass the event along, false to consume it
*/
Keyboard.prototype.onSoftKeyDown = function(sSoftCode, fDown)
Keyboard.prototype.onSoftKeyDown = function(softCode, fDown)
{
var i = this.indexOfSoftKey(softCode);
if (fDown) {
// this.println(sSoftCode + " down");
this.keysPressed[sSoftCode] = Date.now();
delete this.keysToRelease[sSoftCode];
} else {
// this.println(sSoftCode + " up");
var msDown = this.keysPressed[sSoftCode];
if (msDown) {
var msElapsed = Date.now() - msDown;
if (msElapsed < Keyboard.MINPRESSTIME) {
// this.println(sSoftCode + " released after only " + msElapsed + "ms");
this.keysToRelease[sSoftCode] = msDown;
this.checkSoftKeysToRelease();
return true;
// this.println(softCode + " down");
if (i < 0) {
this.aKeysPressed.push({
softCode: softCode,
msDown: Date.now(),
fAutoRelease: false
});
} else {
this.aKeysPressed[i].msDown = Date.now();
this.aKeysPressed[i].fAutoRelease = false;
}
} else if (i >= 0) {
// this.println(softCode + " up");
if (!this.aKeysPressed[i].fAutoRelease) {
var msDown = this.aKeysPressed[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.checkSoftKeysToRelease();
return true;
}
}
}
delete this.keysPressed[sSoftCode];
this.aKeysPressed.splice(i, 1);
} else {
// this.println(softCode + " up with no down?");
}
if (this.chipset) {
switch(sSoftCode) {
switch(softCode) {
case '1p':
this.chipset.updateStatus1(ChipSet.SI1978.STATUS1.P1, fDown);
break;
@ -634,21 +815,30 @@ Keyboard.prototype.onSoftKeyDown = function(sSoftCode, fDown)
*/
Keyboard.prototype.checkSoftKeysToRelease = function()
{
var i = 0;
var msDelayMin = -1;
var asSoftCodes = Object.keys(this.keysToRelease);
for (var i = 0; i < asSoftCodes.length; i++) {
var sSoftCode = asSoftCodes[i];
var msDown = this.keysToRelease[sSoftCode];
var msElapsed = Date.now() - msDown;
var msDelay = Keyboard.MINPRESSTIME - msElapsed;
if (msDelay > 0) {
if (msDelayMin < 0 || msDelayMin > msDelay) {
msDelayMin = msDelay;
while (i < this.aKeysPressed.length) {
if (this.aKeysPressed[i].fAutoRelease) {
var softCode = this.aKeysPressed[i].softCode;
var msDown = this.aKeysPressed[i].msDown;
var msElapsed = Date.now() - msDown;
var msDelay = Keyboard.MINPRESSTIME - msElapsed;
if (msDelay > 0) {
if (msDelayMin < 0 || msDelayMin > msDelay) {
msDelayMin = msDelay;
}
} else {
/*
* Because the key is already in the auto-release state, this next call guarantees that the
* key will be removed from the array; a consequence of that removal, however, is that we must
* reset our array index to zero.
*/
this.onSoftKeyDown(softCode, false);
i = 0;
continue;
}
} else {
delete this.keysToRelease[sSoftCode];
this.onSoftKeyDown(sSoftCode, false);
}
i++;
}
if (msDelayMin >= 0) {
var kbd = this;
@ -656,6 +846,26 @@ Keyboard.prototype.checkSoftKeysToRelease = function()
}
};
/**
* inVT100UARTAddress(port, addrFrom)
*
* @this {Keyboard}
* @param {number} port (0x82)
* @param {number} [addrFrom] (not defined if the Debugger is trying to write the specified port)
* @return {number} simulated port value
*/
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];
}
if (!b) b = Keyboard.VT100.KEYLAST;
this.printMessageIO(port, null, addrFrom, "KBDUART.ADDRESS", b);
return b;
};
/**
* outVT100UARTStatus(port, b, addrFrom)
*
@ -666,15 +876,20 @@ Keyboard.prototype.checkSoftKeysToRelease = function()
*/
Keyboard.prototype.outVT100UARTStatus = function(port, b, addrFrom)
{
this.printMessageIO(port, b, addrFrom, "KBDUART.STATUS", null, true);
this.bLEDs = b;
this.updateLEDs();
this.printMessageIO(port, b, addrFrom, "KBDUART.STATUS");
this.bVT100Status = b;
this.updateLEDs(b & Keyboard.VT100.STATUS.LEDS);
if (b & Keyboard.VT100.STATUS.START) {
this.iKeyNext = 0;
this.cpu.requestINTR(1);
}
};
/*
* Port notification tables
*/
Keyboard.VT100.portsInput = {
0x82: Keyboard.prototype.inVT100UARTAddress
};
Keyboard.VT100.portsOutput = {

View file

@ -0,0 +1,519 @@
/**
* @fileoverview Implements the PC8080 SerialPort component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2016-Aug-08
*
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, a computer emulation software project at <http://pcjs.org/>.
*
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see COPYRIGHT in /modules/shared/lib/defines.js).
*
* Some PCjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of PCjs
* for purposes of the GNU General Public License, and the author does not claim any copyright
* as to their contents.
*/
"use strict";
if (NODE) {
var str = require("../../shared/lib/strlib");
var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var Messages = require("./messages");
var ChipSet = require("./chipset");
var State = require("./state");
}
/**
* SerialPort(parmsSerial)
*
* The SerialPort component has the following component-specific (parmsSerial) properties:
*
* adapter: 0 if not defined
*
* binding: name of a control (based on its "binding" attribute) to bind to this port's I/O
*
* tabSize: set to a non-zero number to convert tabs to spaces (applies only to output to
* the above binding); default is 0 (no conversion)
*
* In the future, we may support 'port' and 'irq' properties that allow the machine to define a
* non-standard serial port configuration, instead of only our pre-defined 'adapter' configurations.
*
* NOTE: Since the XSL file defines 'adapter' as a number, not a string, there's no need to use
* parseInt(), and as an added benefit, we don't need to worry about whether a hex or decimal format
* was used.
*
* @constructor
* @extends Component
* @param {Object} parmsSerial
*/
function SerialPort(parmsSerial) {
this.iAdapter = +parmsSerial['adapter'];
switch (this.iAdapter) {
case 0:
this.portBase = 0;
this.nIRQ = 2;
break;
default:
Component.warning("Unrecognized serial adapter #" + this.iAdapter);
return;
}
/**
* consoleOutput becomes a string that records serial port output if the 'binding' property is set to the
* reserved name "console". Nothing is written to the console, however, until a linefeed (0x0A) is output
* or the string length reaches a threshold (currently, 1024 characters).
*
* @type {string|null}
*/
this.consoleOutput = null;
/**
* controlIOBuffer is a DOM element, if any, bound to the port (currently used for output only; see echoByte()).
*
* @type {Object}
*/
this.controlIOBuffer = null;
/*
* If controlIOBuffer is being used AND 'tabSize' is set, then we make an attempt to monitor the characters
* being echoed via echoByte(), maintain a logical column position, and convert any tabs into the appropriate
* number of spaces.
*
* charBOL, if nonzero, is a character to automatically output at the beginning of every line. This probably
* isn't generally useful; I use it internally to preformat serial output.
*/
this.tabSize = parmsSerial['tabSize'];
this.charBOL = parmsSerial['charBOL'];
this.iLogicalCol = 0;
Component.call(this, "SerialPort", parmsSerial, SerialPort, Messages.SERIAL);
var sBinding = parmsSerial['binding'];
if (sBinding == "console") {
this.consoleOutput = "";
} else {
/*
* NOTE: If sBinding is not the name of a valid Control Panel DOM element, this call does nothing.
*/
Component.bindExternalControl(this, sBinding, SerialPort.sIOBuffer);
}
}
/*
* class SerialPort
* property {number} iAdapter
* property {number} portBase
* property {number} nIRQ
* property {Object} controlIOBuffer is a DOM element, if any, bound to the port (for rudimentary output; see echoByte())
*
* NOTE: This class declaration started as a way of informing the code inspector of the controlIOBuffer property,
* which remained undefined until a setBinding() call set it later, but I've since decided that explicitly
* initializing such properties in the constructor is a better way to go -- even though it's more code -- because
* JavaScript compilers are supposed to be happier when the underlying object structures aren't constantly changing.
*
* Besides, I'm not sure I want to get into documenting every property this way, for this or any/every other class,
* let alone getting into which ones should be considered private or protected, because PCjs isn't really a library
* for third-party apps.
*/
Component.subclass(SerialPort);
/*
* Internal name used for the I/O buffer control, if any, that we bind to the SerialPort.
*
* Alternatively, if SerialPort wants to use another component's control (eg, the Panel's
* "print" control), it can specify the name of that control with the 'binding' property.
*
* For that binding to succeed, we also need to know the target component; for now, that's
* been hard-coded to "Panel", in part because that's one of the few components we can rely
* upon initializing before we do, but it would be a simple matter to include a component type
* or ID as part of the 'binding' property as well, if we need more flexibility later.
*/
SerialPort.sIOBuffer = "buffer";
/**
* setBinding(sHTMLType, sBinding, control, sValue)
*
* @this {SerialPort}
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas")
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "buffer")
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
* @param {string} [sValue] optional data value
* @return {boolean} true if binding was successful, false if unrecognized binding request
*/
SerialPort.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
{
var serial = this;
switch (sBinding) {
case SerialPort.sIOBuffer:
this.bindings[sBinding] = this.controlIOBuffer = control;
/*
* By establishing an onkeypress handler here, we make it possible for DOS commands like
* "CTTY COM1" to more or less work (use "CTTY CON" to restore control to the DOS console).
*/
control.onkeydown = function onKeyDown(event) {
/*
* This is required in addition to onkeypress, because it's the only way to prevent
* BACKSPACE (keyCode 8) from being interpreted by the browser as a "Back" operation;
* moreover, not all browsers generate an onkeypress notification for BACKSPACE.
*
* A related problem exists for Ctrl-key combinations in most Windows-based browsers
* (eg, IE, Edge, Chrome for Windows, etc), because keys like Ctrl-C and Ctrl-S have
* special meanings (eg, Copy, Save). To the extent the browser will allow it, we
* attempt to disable that default behavior when this control receives an onkeydown
* event for one of those keys (probably the only event the browser generates for them).
*/
event = event || window.event;
var keyCode = event.keyCode;
if (keyCode === 0x08 || event.ctrlKey && keyCode >= 0x41 && keyCode <= 0x5A) {
if (event.preventDefault) event.preventDefault();
if (keyCode > 0x40) keyCode -= 0x40;
// serial.sendRBR([keyCode]);
}
return true;
};
control.onkeypress = function onKeyPress(event) {
/*
* Browser-independent keyCode extraction; refer to onKeyPress() and the other key event
* handlers in keyboard.js.
*/
event = event || window.event;
var keyCode = event.which || event.keyCode;
// serial.sendRBR([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
* selected keys (eg, the SPACE key, whose default behavior is to scroll the page), we must
* now call it for *all* keys, so that the keyCode isn't added to the control immediately,
* on top of whatever the machine is echoing back, resulting in double characters.
*/
if (event.preventDefault) event.preventDefault();
return true;
};
/*
* Now that we've added an onkeypress handler that calls preventDefault() for ALL keys, the control
* itself no longer needs the "readonly" attribute; we primarily need to remove it for iOS browsers,
* 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:
break;
}
return false;
};
/**
* initBus(cmp, bus, cpu, dbg)
*
* @this {SerialPort}
* @param {Computer} cmp
* @param {Bus} bus
* @param {CPUState} cpu
* @param {Debugger} dbg
*/
SerialPort.prototype.initBus = function(cmp, bus, cpu, dbg)
{
this.bus = bus;
this.cpu = cpu;
this.dbg = dbg;
this.chipset = cmp.getMachineComponent("ChipSet");
bus.addPortInputTable(this, SerialPort.aPortInput, this.portBase);
bus.addPortOutputTable(this, SerialPort.aPortOutput, this.portBase);
this.setReady();
};
/**
* powerUp(data, fRepower)
*
* @this {SerialPort}
* @param {Object|null} data
* @param {boolean} [fRepower]
* @return {boolean} true if successful, false if failure
*/
SerialPort.prototype.powerUp = function(data, fRepower)
{
if (!fRepower) {
if (!data || !this.restore) {
this.reset();
} else {
if (!this.restore(data)) return false;
}
}
return true;
};
/**
* powerDown(fSave, fShutdown)
*
* @this {SerialPort}
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
*/
SerialPort.prototype.powerDown = function(fSave, fShutdown)
{
return fSave? this.save() : true;
};
/**
* reset()
*
* @this {SerialPort}
*/
SerialPort.prototype.reset = function()
{
this.initState();
};
/**
* save()
*
* This implements save support for the SerialPort component.
*
* @this {SerialPort}
* @return {Object}
*/
SerialPort.prototype.save = function()
{
var state = new State(this);
state.set(0, this.saveRegisters());
return state.data();
};
/**
* restore(data)
*
* This implements restore support for the SerialPort component.
*
* @this {SerialPort}
* @param {Object} data
* @return {boolean} true if successful, false if failure
*/
SerialPort.prototype.restore = function(data)
{
return this.initState(data[0]);
};
/**
* initState(data)
*
* @this {SerialPort}
* @param {Array} [data]
* @return {boolean} true if successful, false if failure
*/
SerialPort.prototype.initState = function(data)
{
var i = 0;
if (data === undefined) {
data = [0, 0, 0];
}
this.bData = data[i++];
this.bCommand = data[i++];
this.bBaudRate = data[i++];
return true;
};
/**
* saveRegisters()
*
* @this {SerialPort}
* @return {Array}
*/
SerialPort.prototype.saveRegisters = function()
{
var i = 0;
var data = [];
data[i++] = this.bData;
data[i++] = this.bCommand;
data[i++] = this.bBaudRate;
return data;
};
/**
* 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.bData;
this.printMessageIO(port, null, addrFrom, "DATA", b);
return b;
};
/**
* inCommand(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.inCommand = function(port, addrFrom)
{
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;
};
/**
* echoByte(b)
*
* @this {SerialPort}
* @param {number} b
* @return {boolean} true if echoed, false if not
*/
SerialPort.prototype.echoByte = function(b)
{
if (this.controlIOBuffer) {
if (b == 0x0D) {
this.iLogicalCol = 0;
}
else if (b == 0x08) {
this.controlIOBuffer.value = this.controlIOBuffer.value.slice(0, -1);
/*
* TODO: Back up the correct number of columns if the character erased was a tab.
*/
if (this.iLogicalCol > 0) this.iLogicalCol--;
}
else {
var s = String.fromCharCode(b);
var nChars = (b >= 0x20? 1 : 0);
if (b == 0x09) {
var tabSize = this.tabSize || 8;
nChars = tabSize - (this.iLogicalCol % tabSize);
if (this.tabSize) s = str.pad("", nChars);
}
if (this.charBOL && !this.iLogicalCol && nChars) s = String.fromCharCode(this.charBOL) + s;
this.controlIOBuffer.value += s;
this.controlIOBuffer.scrollTop = this.controlIOBuffer.scrollHeight;
this.iLogicalCol += nChars;
}
return true;
}
if (this.consoleOutput != null) {
if (b == 0x0A || this.consoleOutput.length >= 1024) {
this.println(this.consoleOutput);
this.consoleOutput = "";
}
if (b != 0x0A) {
this.consoleOutput += String.fromCharCode(b);
}
return true;
}
return false;
};
/*
* Port input notification table
*/
SerialPort.aPortInput = {
0x0: SerialPort.prototype.inData,
0x1: SerialPort.prototype.inCommand
};
/*
* Port output notification table
*/
SerialPort.aPortOutput = {
0x0: SerialPort.prototype.outData,
0x1: SerialPort.prototype.outCommand,
0x2: SerialPort.prototype.outBaudRate
};
/**
* SerialPort.init()
*
* This function operates on every HTML element of class "serial", extracting the
* JSON-encoded parameters for the SerialPort constructor from the element's "data-value"
* attribute, invoking the constructor to create a SerialPort component, and then binding
* any associated HTML controls to the new component.
*/
SerialPort.init = function()
{
var aeSerial = Component.getElementsByClass(document, PC8080.APPCLASS, "serial");
for (var iSerial = 0; iSerial < aeSerial.length; iSerial++) {
var eSerial = aeSerial[iSerial];
var parmsSerial = Component.getComponentParms(eSerial);
var serial = new SerialPort(parmsSerial);
Component.bindComponentControls(serial, eSerial, PC8080.APPCLASS);
}
};
/*
* Initialize every SerialPort module on the page.
*/
web.onInit(SerialPort.init);
if (NODE) module.exports = SerialPort;

View file

@ -109,7 +109,7 @@ function Video(parmsVideo, canvas, context, textarea, container)
this.fUseRAM = parmsVideo['bufferRAM'];
var sFormat = parmsVideo['bufferFormat'];
this.nFormat = sFormat && Video.FORMATS[sFormat.toLowerCase()] || Video.FORMAT.UNKNOWN;
this.nFormat = sFormat && Video.FORMATS[sFormat.toUpperCase()] || Video.FORMAT.UNKNOWN;
this.nColsBuffer = parmsVideo['bufferCols'];
this.nRowsBuffer = parmsVideo['bufferRows'];
@ -117,6 +117,7 @@ function Video(parmsVideo, canvas, context, textarea, container)
this.cxCellDefault = this.cxCell = parmsVideo['cellWidth'] || 1;
this.cyCellDefault = this.cyCell = parmsVideo['cellHeight'] || 1;
this.abFontData = null;
this.fDotStretcher = false;
this.nBitsPerPixel = parmsVideo['bufferBits'] || 1;
this.iBitFirstPixel = parmsVideo['bufferLeft'] || 0;
@ -262,7 +263,8 @@ Video.FORMAT = {
};
Video.FORMATS = {
"vt100": Video.FORMAT.VT100
"SI1978": Video.FORMAT.SI1978,
"VT100": Video.FORMAT.VT100
};
@ -395,11 +397,6 @@ Video.prototype.initBus = function(cmp, bus, cpu, dbg)
this.bus = bus;
this.cpu = cpu;
this.dbg = dbg;
this.chipset = cmp.getMachineComponent("ChipSet");
if (!this.nFormat && this.chipset && this.chipset.model == ChipSet.SI1978.MODEL) {
this.nFormat = Video.FORMAT.SI1978;
}
/*
* Allocate the frame buffer (as needed) along with all other buffers.
@ -495,6 +492,7 @@ Video.prototype.createFonts = function(abFontData)
* We retain abFontData in case we have to rebuild the fonts (eg, when we switch from 80 to 132 columns)
*/
this.abFontData = abFontData;
this.fDotStretcher = (this.nFormat == Video.FORMAT.VT100);
this.aFonts[Video.VT100.FONT.NORML] = [
this.createFontVariation(this.cxCell, this.cyCell),
this.createFontVariation(this.cxCell, this.cyCell, this.fUnderline)
@ -561,6 +559,7 @@ Video.prototype.createFontVariation = function(cxCell, cyCell, fUnderline)
var offFontData = iChar * nFontBytesPerChar + ((nFontByteOffset + y) & (nFontBytesPerChar - 1));
var bits = (fUnderline && y == 8? 0xff : this.abFontData[offFontData]);
for (var nRows = 0; nRows < (cyCell / this.cyCell); nRows++) {
var bitPrev = 0;
for (var x = 0, xDst = x; x < this.cxCell; x++) {
/*
* While x goes from 0 to cxCell-1, obviously we will run out of bits after x is 7;
@ -568,12 +567,14 @@ Video.prototype.createFontVariation = function(cxCell, cyCell, fUnderline)
* (so that line-drawing characters seamlessly connect), we ensure that the effective
* shift count remains stuck at 7 once it reaches 7.
*/
var bit = bits & (0x80 >> (x > 7? 7 : x));
var bitReal = bits & (0x80 >> (x > 7? 7 : x));
var bit = (this.fDotStretcher && !bitReal && bitPrev)? bitPrev : bitReal;
for (var nCols = 0; nCols < (cxCell / this.cxCell); nCols++) {
if (fReverse) bit = !bit;
this.setPixel(imageChar, xDst, yDst, bit? 1 : 0);
xDst++;
}
bitPrev = bitReal;
}
yDst++;
}
@ -1059,19 +1060,26 @@ Video.prototype.updateScreen = function(n)
if (n >= 0) {
if (this.rateInterrupt) {
if (!(n & 1)) {
/*
* On even updates, call cpu.requestINTR(1), and also update our copy of the screen.
*/
this.cpu.requestINTR(1);
/*
* TODO: Incorporate these hard-coded interrupt vector numbers into configuration blocks.
*/
if (this.rateInterrupt == 120) {
if (!(n & 1)) {
/*
* On even updates, call cpu.requestINTR(1), and also update our copy of the screen.
*/
this.cpu.requestINTR(1);
} else {
/*
* On odd updates, call cpu.requestINTR(2), but do NOT update our copy of the screen, because
* the machine has presumably only updated the top half of the frame buffer at this point; it will
* update the bottom half of the frame buffer after acknowledging this interrupt.
*/
this.cpu.requestINTR(2);
fUpdate = false;
}
} else {
/*
* On odd updates, call cpu.requestINTR(2), but do NOT update our copy of the screen, because
* the machine has presumably only updated the top half of the frame buffer at this point; it will
* update the bottom half of the frame buffer after acknowledging this interrupt.
*/
this.cpu.requestINTR(2);
fUpdate = false;
this.cpu.requestINTR(4);
}
}

View file

@ -167,6 +167,9 @@ pre a, code a {
padding-bottom: 1em;
padding-right: 1em; */
}
.common-main blockquote {
text-align: justify;
}
.common-image-gallery {
margin: 0 auto;
text-align: center;

View file

@ -154,6 +154,7 @@
"./modules/pc8080/lib/ram.js",
"./modules/pc8080/lib/keyboard.js",
"./modules/pc8080/lib/video.js",
"./modules/pc8080/lib/serialport.js",
"./modules/pc8080/lib/debugger.js",
"./modules/pc8080/lib/state.js",
"./modules/pc8080/lib/computer.js",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long