--- vt102/vt102 2014/12/03 10:35:59 1.22 +++ vt102/vt102 2014/12/03 11:09:51 1.23 @@ -107,14 +107,15 @@ # 8085 CPU registers and I/O support # 8080/8085 registers -my ($A, $B, $C, $D, $E, $H, $L); -my ($PC, $SP, $IFF, $FA, $FZ, $FS, $FP, $FC); +my ($A, $B, $C, $D, $E, $H, $L); # 8 bit general purpose +my ($PC, $SP, $IFF); # program counter, stack pointer, interrupt flag +my ($FA, $FZ, $FS, $FP, $FC); # condition codes (psw) + +my $RST = 0; # pending interrupts (8259 interrupt controller) +my $INTMASK = 7; # 8085 half interrupt mask +my $INTPEND = 0; # 8085 half interrupts pending -my $RST = 0; # 8080 pending interrupts -my $INTMASK = 7; # 8085 half interrupts -my $INTPEND = 0; # 8085 half interrupts - -my $CLK; # rather inexact clock +my $CLK; # rather inexact clock, counts extended basic blocks ############################################################################# # the dreaded NVR1400 chip. not needed to get it going, but provided anyway @@ -136,33 +137,31 @@ sub { }, # 7 standby ); -my @NVR_BITIDX; -$NVR_BITIDX[1 << $_] = 9 - $_ for 0..9; +my @NVR_BITIDX; $NVR_BITIDX[1 << $_] = 9 - $_ for 0..9; # the nvr1400 state machine. what a monster sub nvr() { my $a1 = $NVR_BITIDX[(~$NVRADDR ) & 0x3ff]; my $a0 = $NVR_BITIDX[(~$NVRADDR >> 10) & 0x3ff]; -# printf "NVR %02x A %020b %d %d D %02x\n", $NVRLATCH, $NVRADDR & 0xfffff, $a1, $a0, $NVRDATA; - $NVRCMD[($NVRLATCH >> 1) & 7]($a1 * 10 + $a0, $NVRLATCH & 1) } ############################################################################# # I/O ports - output -my $DC11_REVERSE = 0; +my $DC11_REVERSE = 0; # light background? my $XON = 1; # false if terminal wants us to pause my $PUSARTCMD; -my @KXMIT; # current scan queue -my %KXMIT; # currently pressed keys -my @KQUEUE; # key event queue -my $KXCNT; # count for debouncew -my @PUSARTRECV; -my $KSTATUS; +my $KSTATUS; # keyboard status (click + scan flag + leds) +my @KXMIT; # current scan queue +my %KXMIT; # currently pressed keys +my @KQUEUE; # key event queue +my $KXCNT; # count for debouncew + +my @PUSARTRECV; # serial input (to terminal) queue sub out_00 { # pusartdata # handle xon/xoff, but also pass it through @@ -179,7 +178,7 @@ $INTPEND |= 1; } -sub out_01 { +sub out_01 { # pusartcmd $PUSARTCMD = shift; $INTPEND |= 1 if $PUSARTCMD & 0x01; # VT102, 5.5 txrdy @@ -188,17 +187,17 @@ sub out_02 { } # baudrate generator -sub out_23 { } # unknown -sub out_27 { } # unknown -sub out_2f { } # unknown, connected to in 0f +sub out_23 { } # vt102 unknown +sub out_27 { } # vt102 unknown +sub out_2f { } # vt102 unknown, connected to in 0f sub out_42 { } # brightness -sub out_62 { +sub out_62 { # nvr latch register (4 bits) $NVRLATCH = shift; } -sub out_a2 { +sub out_a2 { # device control 011 my $dc11 = 0x0f & shift; $DC11_REVERSE = 1 if $dc11 == 0b1010; @@ -206,16 +205,14 @@ } sub out_c2 { } # unknown -sub out_d2 { } # 0..3 == 80c/132c/60hz/50hz - -sub out_82 { - # keyboard +sub out_d2 { } # device control 012, 0..3 == 80c/132c/60hz/50hz - # CLICK STARTSCAN ONLINE LOCKED | CTS DSR INS L1 - # CLICK STARTSCAN ONLINE LOCKED | L1 L2 L3 L4 +sub out_82 { # keyboard txmit + # CLICK STARTSCAN ONLINE LOCKED | L1 L2 L3 L4 (vt100) + # CLICK STARTSCAN ONLINE LOCKED | CTS DSR INS L1 (vt102) $KSTATUS = $_[0]; - # start new scan unless scan in progress + # start new scan unless scan is in progress if (($_[0] & 0x40) && !@KXMIT) { # do not reply with keys in locked mode # or during post (0xff), @@ -247,8 +244,8 @@ ############################################################################# # I/O ports - input -my $NVRBIT; -my $LBA6; # twice the frequenxy of LBA7 +my $NVRBIT; # the current nvr data bit +my $LBA6; # twice the frequenxy of LBA7 sub in_00 { # pusart data # interrupt not generated here, because infinite @@ -262,7 +259,7 @@ 0x85 + (@PUSARTRECV && 0x02) } -sub in_22 { # modem buffer(?) +sub in_22 { # modem buffer # wild guess: -CTS -SPDI -RI -CD 0 0 0 0 0x20 } @@ -301,7 +298,7 @@ my $x; # dummy scratchpad for opcodes -sub sf { # set flags (ZSC - AP not implemented) +sub sf { # set flags, full version (ZSC - AP not implemented) $FS = $_[0] & 0x080; $FZ = !($_[0] & 0x0ff); $FC = $_[0] & 0x100; @@ -309,25 +306,26 @@ $_[0] &= 0xff; } -sub sf8 { # set flags (ZSC - AP not implemented) +sub sf8 { # set flags, for 8-bit results (ZSC - AP not implemented) $FS = $_[0] & 0x080; $FZ = !($_[0] & 0x0ff); $FC = 0; } -sub sf_nc { # set flags except carry +sub sf_nc { # set flags, except carry $FS = $_[0] & 0x080; $FZ = ($_[0] & 0x0ff) == 0; $_[0] &= 0xff; } +# opcode table my @op = map { sprintf "status(); die 'unknown op %02x'", $_ } 0x00 .. 0xff; -my @reg = qw($B $C $D $E $H $L $M[$H*256+$L] $A); -my @cc = ('if !$FZ', 'if $FZ', 'if !$FC', 'if $FC', ';die', ';die', 'if !$FS', 'if $FS'); # die == unimplemented $FP parity +my @reg = qw($B $C $D $E $H $L $M[$H*256+$L] $A); # r/m encoding +my @cc = ('!$FZ', '$FZ', '!$FC', '$FC', 'die;', 'die;', '!$FS', '$FS'); # cc encoding. die == unimplemented $FP parity -$op[0x00] = ''; +$op[0x00] = ''; # nop # mov r,r / r,M / M,r for my $s (0..7) { @@ -377,12 +375,9 @@ $op[0x07] = ' $FC = $A & 0x80; $A = (($A << 1) + ($FC && 0x01)) & 0xff '; # rlc $op[0x17] = ' ($FC, $A) = ($A & 0x80, (($A << 1) + ($FC && 0x01)) & 0xff)'; # ral - $op[0x0f] = ' $FC = $A & 0x01; $A = ($A >> 1) | ($FC && 0x80) '; # rrc $op[0x1f] = ' ($FC, $A) = ($A & 0x01, ($A >> 1) | ($FC && 0x80))'; # rar -$op[0x2f] = '$A ^= 0xff'; # cma - # getting this insn wrong (its the only 16 bit insn to modify flags) # wasted three of my best days with mindless vt102 rom reverse engineering sub dad { @@ -397,6 +392,8 @@ $op[0x29] = 'dad $H * 256 + $L'; # dad $op[0x39] = 'dad $SP '; # dad +$op[0x2f] = '$A ^= 0xff'; # cma + $op[0x80 + $_] = 'sf $A += + ' . $reg[$_] for 0..7; # add $op[0x88 + $_] = 'sf $A += ($FC && 1) + ' . $reg[$_] for 0..7; # adc $op[0x90 + $_] = 'sf $A -= + ' . $reg[$_] for 0..7; # sub @@ -405,16 +402,16 @@ $op[0xa8 + $_] = 'sf8 $A ^= ' . $reg[$_] for 0..7; # xra $op[0xb0 + $_] = 'sf8 $A |= ' . $reg[$_] for 0..7; # ora $op[0xb8 + $_] = 'sf $x = $A - ' . $reg[$_] for 0..7; # cmp -# possible todo: optimize ora a, maybe xra a +# possible todo: optimize ora a, maybe xra a, possibly ana $op[0xc6] = 'sf $A += IMM8'; # adi -# ce ACI NYI, apparently unused $op[0xd6] = 'sf $A -= IMM8'; # sui -# de SBI NYI, apparently unused $op[0xe6] = 'sf8 $A &= IMM8'; # ani $op[0xee] = 'sf8 $A ^= IMM8'; # xri $op[0xf6] = 'sf8 $A |= IMM8'; # ori $op[0xfe] = 'sf $A - IMM8'; # cpi +# ce ACI NYI, apparently unused +# de SBI NYI, apparently unused $op[0xc5] = 'PUSH $B; PUSH $C'; $op[0xd5] = 'PUSH $D; PUSH $E'; @@ -426,13 +423,13 @@ $op[0xe1] = '($L, $H) = (POP, POP)'; # pop $op[0xf1] = '($x, $A) = (POP, POP); ($FS, $FZ, $FA, $FP, $FC) = ($x & 0x80, $x & 0x40, $x & 0x10, $x & 0x04, $x & 0x01)'; # pop psw -$op[0xc2 + $_ * 8] = 'BRA IMM16 ' . $cc[$_] for 0..7; # jcc +$op[0xc2 + $_ * 8] = 'BRA IMM16 if ' . $cc[$_] for 0..7; # jcc $op[0xc3] = 'JMP IMM16'; # jmp -$op[0xc4 + $_ * 8] = '(PUSH PC >> 8), (PUSH PC & 0xff), (BRA IMM16) ' . $cc[$_] for 0..7; # ccc +$op[0xc4 + $_ * 8] = '(PUSH PC >> 8), (PUSH PC & 0xff), (BRA IMM16) if ' . $cc[$_] for 0..7; # ccc $op[0xcd] = '(PUSH PC >> 8), (PUSH PC & 0xff), (BRA IMM16)'; # call -$op[0xc0 + $_ * 8] = 'BRA POP + POP * 256 ' . $cc[$_] for 0..7; # rcc +$op[0xc0 + $_ * 8] = 'BRA POP + POP * 256 if ' . $cc[$_] for 0..7; # rcc $op[0xc9] = 'JMP POP + POP * 256'; # ret $op[0xc7 + $_ * 8] = "JMP $_ * 8" for 0..7; # rst @@ -444,19 +441,19 @@ $op[0x3f] = '$FC = !$FC'; # cmc $op[0xd3] = 'OUT'; # out -$op[0xdb] = 'IN'; # in +$op[0xdb] = 'IN'; # in $op[0xeb] = '($D, $E, $H, $L) = ($H, $L, $D, $E)'; # xchg # e3 xthl NYI # @ 917b in e69, hl <-> (sp) -$op[0x20] = '$A = $INTPEND * 16 + $INTMASK + ($IFF && 8)'; # rim (incomplete) -$op[0x30] = '$INTMASK = $A & 7 if $A & 8'; # sim (incomplete) +$op[0x20] = '$A = $INTPEND * 16 + $INTMASK + ($IFF && 8)'; # rim (8085, incomplete) +$op[0x30] = '$INTMASK = $A & 7 if $A & 8'; # sim (8085, incomplete) -$op[0xf3] = '$IFF = 0'; # DI -$op[0xfb] = '$IFF = 1'; # EI +$op[0xf3] = '$IFF = 0'; # di +$op[0xfb] = '$IFF = 1'; # ei -# yeah, the fucking setup screens actually use daa... +# yeah, the fucking setup screen actually uses daa... $op[0x27] = ' my ($h, $l); @@ -474,7 +471,7 @@ '; # daa, almost certainly borked, also, acarry not set by sf ############################################################################# -# print cpu status for debugging purposes +# debug # print cpu status, for debugging sub status { @@ -496,7 +493,7 @@ binmode STDOUT; -my @CHARMAP = ( +my @CHARMAP = ( # acschars / chars 0..31 " " , "\x{29eb}", "\x{2592}", "\x{2409}", "\x{240c}", "\x{240d}", "\x{240a}", "\x{00b0}", "\x{00b1}", "\x{2424}", "\x{240b}", "\x{2518}", @@ -627,7 +624,7 @@ my $KEYMATCH = join "|", map quotemeta, reverse sort keys %KEYMAP; $KEYMATCH = qr{^($KEYMATCH)}s; -my %KMOD; +my %KMOD; # currently pressed modifier keys sub key { my ($key) = @_; @@ -715,7 +712,7 @@ ############################################################################# # the actual hardware simulator -my @ICACHE; # compiled instruction cache +my @ICACHE; # compiled instruction/basic block cache my $POWERSAVE; # powersave counter @@ -724,6 +721,7 @@ (vec $RIN, 0, 1) = 1 if $KBD; (vec $RIN, fileno $PTY, 1) = 1 if $PTY; +# the cpu. while () { # execute extended basic blocks $PC = ($ICACHE[$PC] ||= do { @@ -747,8 +745,8 @@ s/\bBRA\b/return/g; # conditional jump s/\bJMP\b(.*)/$1\x00/sg; # unconditional jump - s/\bIN\b/ sprintf "\$A = in_%02x", $M[$pc++]/xge; - s/\bOUT\b/sprintf "out_%02x \$A ", $M[$pc++]/xge; + s/\bIN\b/ sprintf "\$A = in_%02x", $M[$pc++]/xge; # in insns call in_HEX + s/\bOUT\b/sprintf "out_%02x \$A ", $M[$pc++]/xge; # out likewise } $insn .= "$op;\n"; @@ -771,40 +769,36 @@ if (select $x = $RIN, undef, undef, $POWERSAVE < 100 ? 0 : 0.2) { # pty/serial I/O if ($PTY && (vec $x, fileno $PTY, 1) && (@PUSARTRECV < 128) && !@KQUEUE) { - my $rin = ""; (vec $rin, fileno $PTY, 1) = 1; - - if (select $rin, undef, undef, 0) { - sysread $PTY, my $buf, 256; + sysread $PTY, my $buf, 256; - # linux don't do cs7 and/or parity anymore, so we need to filter - # out xoff characters to avoid freezes. - push @PUSARTRECV, grep { ($_ & 0x7f) != 0x13 } unpack "C*", $buf; - } + # linux don't do cs7 and/or parity anymore, so we need to filter + # out xoff characters to avoid freezes. + push @PUSARTRECV, grep { ($_ & 0x7f) != 0x13 } unpack "C*", $buf; } # keyboard input if ($KBD && (vec $x, 0, 1)) { + # to avoid non-blocking mode on stdin (and stty min 0), we + # just read byte-by-byte after a select says there is data. while (select my $rin = "\x01", undef, undef, 0) { sysread STDIN, $STDIN_BUF, 1, length $STDIN_BUF or last; } stdin_parse if length $STDIN_BUF; - $POWERSAVE = 0; } - $POWERSAVE = 0; + $POWERSAVE = 0; # activity } else { ++$POWERSAVE unless @PUSARTRECV || @KQUEUE; } } - # kick off various interrupts - + # kick off serial input interrupt quite often $RST |= 2 if @PUSARTRECV && $XON; # VT100, but works on vt102, too (probably not used on real hardware though) #$INTPEND |= 2 if @PUSARTRECV && $XON; # VT102, 6.5 rxrdy - # kick off vertical retrace form time to time + # kick off vertical retrace interrupt from time to time unless ($CLK & 0x1ff) { $RST |= 4; # vertical retrace } @@ -815,7 +809,9 @@ } } - # the interrupt logic + # the interrupt logic - we only interrupt after basic blocks + # which, as a side effect, ensures that we don't interrupt + # "ei; ret" sequences and thus reduce the risk of stack overflows. if (($RST || ($INTPEND & ~$INTMASK)) && $IFF) { # rst 1 kbd data available # rst 2 pusart xmit+recv flag @@ -839,6 +835,7 @@ die; } + # jump to the interrupt vector $M[--$SP] = $PC >> 8; $M[--$SP] = $PC & 0xff; $PC = $vec;