… | |
… | |
21 | #use common::sense; |
21 | #use common::sense; |
22 | |
22 | |
23 | my $VT102 = 1; |
23 | my $VT102 = 1; |
24 | my $VT131 = 0; |
24 | my $VT131 = 0; |
25 | my $AVO = 1; |
25 | my $AVO = 1; |
26 | my $KBD = 1; |
|
|
27 | |
26 | |
28 | if ($ARGV[0] =~ /^-?-vt100$/) { |
27 | shift, ($VT102 = 0), ($AVO = 0) if $ARGV[0] =~ /^-?-vt100$/; |
29 | shift; $VT102 = 0; $AVO = 0; |
28 | shift, ($VT102 = 0) if $ARGV[0] =~ /^-?-vt100\+avo$/; |
30 | } |
29 | shift if $ARGV[0] =~ /^-?-vt102$/; |
|
|
30 | shift, ($VT131 = 1) if $ARGV[0] =~ /^-?-vt131$/; |
31 | |
31 | |
32 | if ($ARGV[0] =~ /^-?-vt100\+avo$/) { |
32 | # vt100 wps = word processing roms |
33 | shift; $VT102 = 0; $AVO = 1; |
33 | # vt101 = vt102 - avo, but custom rom? really? |
34 | } |
34 | # vt103 = vt100 + tu58 tape drive |
35 | |
35 | # vt125 = vt100 + gpo graphics processor |
36 | if ($ARGV[0] =~ /^-?-vt102$/) { |
36 | # vt132 = vt100 + avo, stp |
37 | shift; # default |
37 | # vt180 = vt100 + z80 cp/m |
38 | } |
|
|
39 | |
|
|
40 | if ($ARGV[0] =~ /^-?-vt131$/) { |
|
|
41 | shift; $VT131 = 1; |
|
|
42 | } |
|
|
43 | |
38 | |
44 | if ($ARGV[0] =~ /^-/) { |
39 | if ($ARGV[0] =~ /^-/) { |
45 | die <<EOF; |
40 | die <<EOF; |
46 | |
41 | |
47 | VT102, A VT100/101/102/131 SIMULATOR |
42 | VT102, A VT100/102/131 SIMULATOR |
48 | |
43 | |
49 | Usage: |
44 | Usage: |
50 | |
45 | |
51 | $0 [option] [program [args]] |
46 | $0 [option] [program [args]] |
52 | |
47 | |
… | |
… | |
84 | } |
79 | } |
85 | |
80 | |
86 | ############################################################################# |
81 | ############################################################################# |
87 | # ROM/hardware init |
82 | # ROM/hardware init |
88 | |
83 | |
|
|
84 | my $PTY; # the pty we allocated, if any |
|
|
85 | my $KBD = 1; |
|
|
86 | |
89 | my $ROMS = do { |
87 | my $ROMS = do { |
90 | binmode DATA; |
88 | binmode DATA; |
91 | local $/; |
89 | local $/; |
92 | <DATA> |
90 | <DATA> |
93 | }; |
91 | }; |
94 | |
92 | |
95 | 0x6801 == length $ROMS or die "corrupted rom image"; |
93 | 0x6801 == length $ROMS or die "corrupted rom image"; |
96 | |
|
|
97 | binmode STDOUT; |
|
|
98 | |
94 | |
99 | my @M = (0xff) x 65536; # main memory, = (0xff) x 65536; |
95 | my @M = (0xff) x 65536; # main memory, = (0xff) x 65536; |
100 | |
96 | |
101 | # populate mem with rom contents |
97 | # populate mem with rom contents |
102 | if ($VT102) { |
98 | if ($VT102) { |
… | |
… | |
108 | } |
104 | } |
109 | |
105 | |
110 | ############################################################################# |
106 | ############################################################################# |
111 | # 8085 CPU registers and I/O support |
107 | # 8085 CPU registers and I/O support |
112 | |
108 | |
113 | my $PTY; # the pty we allocated, if any |
|
|
114 | |
|
|
115 | # 8080/8085 registers |
109 | # 8080/8085 registers |
116 | # b, c, d, e, h, l, a |
|
|
117 | my ($A, $B, $C, $D, $E, $H, $L, $A); |
110 | my ($A, $B, $C, $D, $E, $H, $L); |
118 | my ($PC, $SP, $IFF, $FA, $FZ, $FS, $FP, $FC); |
111 | my ($PC, $SP, $IFF, $FA, $FZ, $FS, $FP, $FC); |
119 | |
112 | |
120 | my $RST = 0; # 8080 pending interrupts |
113 | my $RST = 0; # 8080 pending interrupts |
121 | my $INTMASK = 7; # 8085 half interrupts |
114 | my $INTMASK = 7; # 8085 half interrupts |
122 | my $INTPEND = 0; # 8085 half interrupts |
115 | my $INTPEND = 0; # 8085 half interrupts |
123 | |
|
|
124 | my $x; # dummy temp for instructions |
|
|
125 | |
116 | |
126 | my $CLK; # rather inexact clock |
117 | my $CLK; # rather inexact clock |
127 | |
118 | |
128 | ############################################################################# |
119 | ############################################################################# |
129 | # the dreaded NVR1400 chip. not needed to get it going, but provided anyway |
120 | # the dreaded NVR1400 chip. not needed to get it going, but provided anyway |
… | |
… | |
143 | sub { $NVR[$_[0]] = 0x3fff; }, # 5 erase |
134 | sub { $NVR[$_[0]] = 0x3fff; }, # 5 erase |
144 | sub { $NVRDATA = $NVR[$_[0]]; }, # 6 read |
135 | sub { $NVRDATA = $NVR[$_[0]]; }, # 6 read |
145 | sub { }, # 7 standby |
136 | sub { }, # 7 standby |
146 | ); |
137 | ); |
147 | |
138 | |
148 | my @bitidx; |
139 | my @NVR_BITIDX; |
149 | $bitidx[1 << $_] = 9 - $_ for 0..9; |
140 | $NVR_BITIDX[1 << $_] = 9 - $_ for 0..9; |
150 | |
141 | |
151 | # the nvr1400 state machine. what a monster |
142 | # the nvr1400 state machine. what a monster |
152 | sub nvr() { |
143 | sub nvr() { |
153 | my $a1 = $bitidx[(~$NVRADDR ) & 0x3ff]; |
144 | my $a1 = $NVR_BITIDX[(~$NVRADDR ) & 0x3ff]; |
154 | my $a0 = $bitidx[(~$NVRADDR >> 10) & 0x3ff]; |
145 | my $a0 = $NVR_BITIDX[(~$NVRADDR >> 10) & 0x3ff]; |
155 | |
146 | |
156 | # printf "NVR %02x A %020b %d %d D %02x\n", $NVRLATCH, $NVRADDR & 0xfffff, $a1, $a0, $NVRDATA; |
147 | # printf "NVR %02x A %020b %d %d D %02x\n", $NVRLATCH, $NVRADDR & 0xfffff, $a1, $a0, $NVRDATA; |
157 | |
148 | |
158 | $NVRCMD[($NVRLATCH >> 1) & 7]($a1 * 10 + $a0, $NVRLATCH & 1) |
149 | $NVRCMD[($NVRLATCH >> 1) & 7]($a1 * 10 + $a0, $NVRLATCH & 1) |
159 | } |
150 | } |
… | |
… | |
306 | sub in_1b { 0xff } # vt102 unknown |
297 | sub in_1b { 0xff } # vt102 unknown |
307 | |
298 | |
308 | ############################################################################# |
299 | ############################################################################# |
309 | # 8085 cpu opcodes and flag handling |
300 | # 8085 cpu opcodes and flag handling |
310 | |
301 | |
|
|
302 | my $x; # dummy scratchpad for opcodes |
|
|
303 | |
311 | sub sf { # set flags (ZSC - AP not implemented) |
304 | sub sf { # set flags (ZSC - AP not implemented) |
312 | $FS = $_[0] & 0x080; |
305 | $FS = $_[0] & 0x080; |
313 | $FZ = !($_[0] & 0x0ff); |
306 | $FZ = !($_[0] & 0x0ff); |
314 | $FC = $_[0] & 0x100; |
307 | $FC = $_[0] & 0x100; |
315 | |
308 | |
… | |
… | |
413 | $op[0xb0 + $_] = 'sf8 $A |= ' . $reg[$_] for 0..7; # ora |
406 | $op[0xb0 + $_] = 'sf8 $A |= ' . $reg[$_] for 0..7; # ora |
414 | $op[0xb8 + $_] = 'sf $x = $A - ' . $reg[$_] for 0..7; # cmp |
407 | $op[0xb8 + $_] = 'sf $x = $A - ' . $reg[$_] for 0..7; # cmp |
415 | # possible todo: optimize ora a, maybe xra a |
408 | # possible todo: optimize ora a, maybe xra a |
416 | |
409 | |
417 | $op[0xc6] = 'sf $A += IMM8'; # adi |
410 | $op[0xc6] = 'sf $A += IMM8'; # adi |
418 | # ce ADI NYI |
411 | # ce ACI NYI, apparently unused |
419 | $op[0xd6] = 'sf $A -= IMM8'; # sui |
412 | $op[0xd6] = 'sf $A -= IMM8'; # sui |
420 | # de SBI NYI |
413 | # de SBI NYI, apparently unused |
421 | $op[0xe6] = 'sf8 $A &= IMM8'; # ani |
414 | $op[0xe6] = 'sf8 $A &= IMM8'; # ani |
422 | $op[0xee] = 'sf8 $A ^= IMM8'; # xri |
415 | $op[0xee] = 'sf8 $A ^= IMM8'; # xri |
423 | $op[0xf6] = 'sf8 $A |= IMM8'; # ori |
416 | $op[0xf6] = 'sf8 $A |= IMM8'; # ori |
424 | $op[0xfe] = 'sf $A - IMM8'; # cpi |
417 | $op[0xfe] = 'sf $A - IMM8'; # cpi |
425 | |
418 | |
… | |
… | |
443 | $op[0xc9] = 'JMP POP + POP * 256'; # ret |
436 | $op[0xc9] = 'JMP POP + POP * 256'; # ret |
444 | |
437 | |
445 | $op[0xc7 + $_ * 8] = "JMP $_ * 8" for 0..7; # rst |
438 | $op[0xc7 + $_ * 8] = "JMP $_ * 8" for 0..7; # rst |
446 | |
439 | |
447 | $op[0xe9] = 'JMP $H * 256 + $L'; # pchl |
440 | $op[0xe9] = 'JMP $H * 256 + $L'; # pchl |
448 | # f9 SPHL NYI |
441 | # f9 SPHL NYI, apparently unused |
449 | |
442 | |
450 | $op[0x37] = '$FC = 1 '; # stc |
443 | $op[0x37] = '$FC = 1 '; # stc |
451 | $op[0x3f] = '$FC = !$FC'; # cmc |
444 | $op[0x3f] = '$FC = !$FC'; # cmc |
452 | |
445 | |
453 | $op[0xd3] = 'OUT'; # out |
446 | $op[0xd3] = 'OUT'; # out |
454 | $op[0xdb] = 'IN'; # in |
447 | $op[0xdb] = 'IN'; # in |
455 | |
448 | |
456 | $op[0xeb] = '($D, $E, $H, $L) = ($H, $L, $D, $E)'; # xchg |
449 | $op[0xeb] = '($D, $E, $H, $L) = ($H, $L, $D, $E)'; # xchg |
457 | |
450 | |
458 | # e3 xthl NYI # @ 917b, hl <-> (sp) |
451 | # e3 xthl NYI # @ 917b in e69, hl <-> (sp) |
459 | |
452 | |
460 | $op[0x20] = '$A = $INTPEND * 16 + $INTMASK + ($IFF && 8)'; # rim (incomplete) |
453 | $op[0x20] = '$A = $INTPEND * 16 + $INTMASK + ($IFF && 8)'; # rim (incomplete) |
461 | $op[0x30] = '$INTMASK = $A & 7 if $A & 8'; # sim (incomplete) |
454 | $op[0x30] = '$INTMASK = $A & 7 if $A & 8'; # sim (incomplete) |
462 | |
455 | |
463 | $op[0xf3] = '$IFF = 0'; # DI |
456 | $op[0xf3] = '$IFF = 0'; # DI |
… | |
… | |
498 | $M[$PC], $op[$M[$PC]]; |
491 | $M[$PC], $op[$M[$PC]]; |
499 | } |
492 | } |
500 | |
493 | |
501 | ############################################################################# |
494 | ############################################################################# |
502 | # video emulation |
495 | # video emulation |
|
|
496 | |
|
|
497 | binmode STDOUT; |
503 | |
498 | |
504 | my @CHARMAP = ( |
499 | my @CHARMAP = ( |
505 | " " , "\x{29eb}", "\x{2592}", "\x{2409}", |
500 | " " , "\x{29eb}", "\x{2592}", "\x{2409}", |
506 | "\x{240c}", "\x{240d}", "\x{240a}", "\x{00b0}", |
501 | "\x{240c}", "\x{240d}", "\x{240a}", "\x{00b0}", |
507 | "\x{00b1}", "\x{2424}", "\x{240b}", "\x{2518}", |
502 | "\x{00b1}", "\x{2424}", "\x{240b}", "\x{2518}", |
… | |
… | |
695 | my $slave = $PTY->slave; |
690 | my $slave = $PTY->slave; |
696 | |
691 | |
697 | $PTY->set_winsize (24, 80); |
692 | $PTY->set_winsize (24, 80); |
698 | |
693 | |
699 | unless (fork) { |
694 | unless (fork) { |
|
|
695 | $ENV{LC_ALL} = "C"; |
700 | $ENV{TERM} = $VT102 ? "vt102" : "vt100"; |
696 | $ENV{TERM} = $VT102 ? "vt102" : "vt100"; |
701 | |
697 | |
702 | close $PTY; |
698 | close $PTY; |
703 | |
699 | |
704 | open STDIN , "<&", $slave; |
700 | open STDIN , "<&", $slave; |
… | |
… | |
718 | |
714 | |
719 | ############################################################################# |
715 | ############################################################################# |
720 | # the actual hardware simulator |
716 | # the actual hardware simulator |
721 | |
717 | |
722 | my @ICACHE; # compiled instruction cache |
718 | my @ICACHE; # compiled instruction cache |
|
|
719 | |
|
|
720 | my $POWERSAVE; # powersave counter |
|
|
721 | |
|
|
722 | my $RIN; # libev for the less well-off |
|
|
723 | |
|
|
724 | (vec $RIN, 0, 1) = 1 if $KBD; |
|
|
725 | (vec $RIN, fileno $PTY, 1) = 1 if $PTY; |
723 | |
726 | |
724 | while () { |
727 | while () { |
725 | # execute extended basic blocks |
728 | # execute extended basic blocks |
726 | $PC = ($ICACHE[$PC] ||= do { |
729 | $PC = ($ICACHE[$PC] ||= do { |
727 | my $pc = $PC; |
730 | my $pc = $PC; |
… | |
… | |
758 | eval "use integer; sub { $insn }" or die "$insn: $@" |
761 | eval "use integer; sub { $insn }" or die "$insn: $@" |
759 | })->(); |
762 | })->(); |
760 | |
763 | |
761 | ++$CLK; |
764 | ++$CLK; |
762 | |
765 | |
763 | # things we do from time too time only |
766 | # things we do from time to time only |
764 | unless ($CLK & 0xf) { |
767 | unless ($CLK & 0xf) { |
765 | # do I/O |
768 | # do I/O |
766 | |
769 | |
767 | unless ($CLK & 0xfff) { |
770 | unless ($CLK & 0xfff) { |
768 | |
771 | if (select $x = $RIN, undef, undef, $POWERSAVE < 100 ? 0 : 0.2) { |
769 | # pty/serial I/O |
772 | # pty/serial I/O |
770 | unless ((@PUSARTRECV >= 128) || @KQUEUE || !$PTY) { |
773 | if ($PTY && (vec $x, fileno $PTY, 1) && (@PUSARTRECV < 128) && !@KQUEUE) { |
771 | my $rin = ""; (vec $rin, fileno $PTY, 1) = 1; |
774 | my $rin = ""; (vec $rin, fileno $PTY, 1) = 1; |
772 | |
775 | |
773 | if (select $rin, undef, undef, 0) { |
776 | if (select $rin, undef, undef, 0) { |
774 | sysread $PTY, my $buf, 256; |
777 | sysread $PTY, my $buf, 256; |
|
|
778 | |
|
|
779 | # linux don't do cs7 and/or parity anymore, so we need to filter |
|
|
780 | # out xoff characters to avoid freezes. |
775 | push @PUSARTRECV, unpack "C*", $buf; |
781 | push @PUSARTRECV, grep { ($_ & 0x7f) != 0x13 } unpack "C*", $buf; |
|
|
782 | } |
776 | } |
783 | } |
777 | } |
|
|
778 | |
784 | |
779 | # keyboard input |
785 | # keyboard input |
780 | if ($KBD) { |
786 | if ($KBD && (vec $x, 0, 1)) { |
781 | while (select my $rin = "\x01", undef, undef, 0) { |
787 | while (select my $rin = "\x01", undef, undef, 0) { |
782 | sysread STDIN, $STDIN_BUF, 1, length $STDIN_BUF |
788 | sysread STDIN, $STDIN_BUF, 1, length $STDIN_BUF |
783 | or last; |
789 | or last; |
|
|
790 | } |
|
|
791 | |
|
|
792 | stdin_parse if length $STDIN_BUF; |
|
|
793 | $POWERSAVE = 0; |
784 | } |
794 | } |
785 | |
795 | |
786 | stdin_parse if length $STDIN_BUF; |
796 | $POWERSAVE = 0; |
|
|
797 | } else { |
|
|
798 | ++$POWERSAVE; |
787 | } |
799 | } |
788 | } |
800 | } |
789 | |
801 | |
790 | # kick off various interrupts |
802 | # kick off various interrupts |
791 | |
803 | |
… | |
… | |
812 | # 6.5 vt125 mb7 read ready (something modem?) |
824 | # 6.5 vt125 mb7 read ready (something modem?) |
813 | # 7.5 vt125 mb7 vblank h(?) |
825 | # 7.5 vt125 mb7 vblank h(?) |
814 | # trap vt125 mbi init h(?) |
826 | # trap vt125 mbi init h(?) |
815 | my $vec; |
827 | my $vec; |
816 | |
828 | |
817 | $x = $INTPEND & ~$INTMASK; |
829 | my $pend = $INTPEND & ~$INTMASK; |
818 | |
830 | |
819 | if ($x & 1) { $vec = 0x2c; $INTPEND &= ~1; |
831 | if ($pend & 1) { $vec = 0x2c; $INTPEND &= ~1; |
820 | } elsif ($x & 2) { $vec = 0x34; $INTPEND &= ~2; |
832 | } elsif ($pend & 2) { $vec = 0x34; $INTPEND &= ~2; |
821 | } elsif ($x & 4) { $vec = 0x3c; $INTPEND &= ~4; |
833 | } elsif ($pend & 4) { $vec = 0x3c; $INTPEND &= ~4; |
822 | # } elsif ($RST ) { $vec = $RST * 8; $RST = 0; # the vt102 firmware doesn't like combined interrupts |
834 | # } elsif ($RST ) { $vec = $RST * 8; $RST = 0; # the vt102 firmware doesn't like combined interrupts |
823 | } elsif ($RST & 1) { $vec = 0x08; $RST &= ~1; # separate is better for vt102 |
835 | } elsif ($RST & 1) { $vec = 0x08; $RST &= ~1; # separate is better for vt102 |
824 | } elsif ($RST & 2) { $vec = 0x10; $RST &= ~2; |
836 | } elsif ($RST & 2) { $vec = 0x10; $RST &= ~2; |
825 | } elsif ($RST & 4) { $vec = 0x20; $RST &= ~4; |
837 | } elsif ($RST & 4) { $vec = 0x20; $RST &= ~4; |
826 | } else { |
838 | } else { |