Merge branch 'next-release'
This commit is contained in:
commit
639cef608b
18 changed files with 6195 additions and 5240 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
--------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
519
modules/pc8080/lib/serialport.js
Normal file
519
modules/pc8080/lib/serialport.js
Normal 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;
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in a new issue