mirror of
https://github.com/pkimpel/retro-b5500.git
synced 2026-04-25 20:01:52 +00:00
Release emulator version 0.15:
1. Initial implementation of a datacom terminal. 2. Initial implementation (read-only) of magnetic tape drives. 3. Further work towards getting P2 to function (but not working yet). 4. Allow device driver classes to be optionally included in the global UI script. 5. Fix callback arguments handling in SetCallback. 6. Decrease width of SPO window slightly. 7. Improve trapping and printing of SPO keystrokes, based on datacom implementation. 8. Minor performance tuning improvements. 9. Dump raw header words in octal in tools/B5500DiskDirList.html script. 10. New wiki pages and several updates to existing ones.
This commit is contained in:
@@ -23561,3 +23561,252 @@ EOF: END COOLOFF;% 38376000
|
||||
HEADER[4].[11:1]~1; END; 38411800
|
||||
END; IF ACCESS = 4 THEN ACCESS := 2; 38411900
|
||||
END;% 38412000
|
||||
END;% 38412100
|
||||
HEADER[4].[43:1]:=FPB[FNUM+3].[15:1]; 38412200
|
||||
IF (NOT REW) OR LOCK OR REL OR TIME THEN 38419000
|
||||
BEGIN 38420000
|
||||
FORMS~HEADER[3]; 38421000
|
||||
STREAM(PF~[FIB[4]],D~FPB[FNUM+2].[18:30],H~[HEADER[3]],S~[T]); 38422000
|
||||
BEGIN SI~PF;SI~SI+5;DS~3 OCT;SI~LOC D;DI~H;DS~8 OCT END; 38423000
|
||||
HEADER[3]~(P(DUP,LOD,SSN))&(P(DUP))[12:30:18]&T[2:38:10]; 38424000
|
||||
END; 38425000
|
||||
IF LOCK OR HEADER[4].[43:1] THEN 38426000
|
||||
BEGIN IF NOT HEADER[4] THEN%FILE IS BEING CREATED 38427000
|
||||
BEGIN 38428000
|
||||
IF KRUNCH THEN KRUNCHER(HEADER); 38429000
|
||||
HEADER[4].[9:3]:=5;% MARK AS NEW FORMAT,ACCESSED 38430000
|
||||
IF JAR[P1MIX,0] < 0 AND FIB[4].[29:1] THEN 38431000
|
||||
% COMPILER CLOSING CODE FILE WITH LOCK *********************************38432000
|
||||
BEGIN 38433000
|
||||
SEG0:=[M[TYPEDSPACE(62,SEGZEROAREAV)]]&30[SIZE];38434000
|
||||
SKEL ~ 31 INX SEG0; T3 ~ JAR[P1MIX,2].[FF]; 38435000
|
||||
% READ IN SEGMENT ZERO 38436000
|
||||
DISKWAIT(-SEG0.[CF],30,HEADER[10]]; 38437000
|
||||
% READ IN SKELETON SHEET 38438000
|
||||
DISKWAIT(-SKEL.[CF],30,T3); 38439000
|
||||
IF SKEL[20]<0 THEN SKEL[20] ~ SEG0[7].[FF]; 38440000
|
||||
IF JAR[P1MIX,2].[8:10]=1 THEN 38441000
|
||||
BEGIN % COMPILE AND GO **********************38442000
|
||||
SKEL[6]:=JAR[P1MIX,6]& 38442100
|
||||
(PRT[P1MIX,3].[8:10]+20)[CTF]; 38442200
|
||||
DISKWAIT(SKEL.[CF],30,T3); 38443000
|
||||
COMPGO ~ TRUE; 38444000
|
||||
END 38445000
|
||||
ELSE 38446000
|
||||
BEGIN % COMPILE TO LIBRARY ******************38447000
|
||||
FOR T1 ~ 15 STEP 1 UNTIL 22 DO 38448000
|
||||
SEG0[T1] ~ SKEL[T1]; 38449000
|
||||
IF (T2 ~ SKEL[13]) = 0 THEN GO TO L3; 38450000
|
||||
SKEL[13] ~ 0; % IN CASE I CALL TERMINATE 38451000
|
||||
DISKWAIT(SKEL.[CF],30,T3); 38452000
|
||||
IF(T1:=DISKADDRESS(MID,FID,FPB[FNUM+3],HEADER[7]:= % (SHM)38453000
|
||||
(*P(DUP))+1,HEADER,0))=0 THEN 38454000
|
||||
FILEMESS(-"DISK ","OVRFLOW",MID,FID,38455000
|
||||
R,D,C); 38456000
|
||||
SEG0[15] ~ T1 ~ HEADER[7]; 38457000
|
||||
L1: DISKWAIT(-SKEL.[CF],30,T2); 38458000
|
||||
FORGETESPDISK(T2); 38459000
|
||||
IF (T2~SKEL[29]) = 0 THEN GO TO L2; 38460000
|
||||
IF(T3:=DISKADDRESS(MID,FID,FPB[FNUM+3],HEADER[7]:= % (SHM)38461000
|
||||
(*P(DUP))+1,HEADER,0))=0 THEN 38462000
|
||||
FILEMESS(-"DISK ","OVRFLOW",MID,FID,38463000
|
||||
R,D,C); 38464000
|
||||
SKEL[29] ~ T3 ~ HEADER[7]; 38465000
|
||||
DISKWAIT(SKEL.[CF],30, 38466000
|
||||
I~HEADER[T1 DIV HEADER[8]+10] + 38467000
|
||||
T1 MOD HEADER[8]); 38468000
|
||||
T1 ~ T3; 38469000
|
||||
GO TO L1; 38470000
|
||||
L2: DISKWAIT(SKEL.[CF],30, 38471000
|
||||
I~HEADER[T1 DIV HEADER[8]+10] + 38472000
|
||||
T1 MOD HEADER[8]); 38473000
|
||||
L3: SEG9[6] ~ P(DUP,LOD,SSN); % "NEW FORMAT" 38474000
|
||||
HEADER[4].[10:1]~1;%MARK AS PROGRAM FILE 38475000
|
||||
DISKWAIT(SEG0.[CF],30,HEADER[10]); 38476000
|
||||
END COPY OF LABEL EQUATION CARDS; 38477000
|
||||
FORGETSPACE(SEG0); 38478000
|
||||
IF HEADER[7]<HEADER[8]-1 THEN 38479000
|
||||
BEGIN FORGETUSERDISK(HEADER[10]+HEADER[7]+1, 38480000
|
||||
HEADER[7]-HEADER[8]+1); 38481000
|
||||
HEADER[8] ~ HEADER[7]+1; 38482000
|
||||
END; 38483000
|
||||
FOR T1:=-1 STEP 1 UNTIL 4 DO 38484000
|
||||
DUMMY :: IF P(.OBJTYPE,T1,+,LOD)=ABS(JAR[P1MIX,0]) THEN 38485000
|
||||
HEADER[4].[36:6]:=T1+2; 38486000
|
||||
END; 38488000
|
||||
HEADER[1]~FORMS&HEADER[3][6:30:18]; 38489000
|
||||
IF (HEADER[2]:=USERCODE[P1MIX]).[1:1] OR 38490000
|
||||
MCP=NOT(-0) THEN HEADER[2]~0; 38491000
|
||||
HEADER[5] := HEADER[6] := 0; 38492000
|
||||
IF COMPGO THEN 38494000
|
||||
BEGIN PRT[P1MIX,@26]~IOD~GETESPDISK; 38495000
|
||||
DISKWAIT(HEADER.[CF],30,IOD); 38496000
|
||||
END ELSE 38497000
|
||||
BEGIN 38498000
|
||||
ENTERUSERFILE(MID,FID,HEADER.[CF]-1); 38499000
|
||||
END; 38500000
|
||||
END;% 38501000
|
||||
END;% 38502000
|
||||
IF REW AND NOT(LOCK OR REL OR TIME) THEN 38503000
|
||||
BEGIN 38503200
|
||||
IF HEADER[4] THEN 38503400
|
||||
IF WRITTENON THEN HEADER[4].[11;1]:=1; 38503600
|
||||
STATE.[39:4]:=2; 38503800
|
||||
END ELSE 38504000
|
||||
BEGIN 38504500
|
||||
HEADER[1]~FORMS&HEADER[3][6:30:18]; 38505000
|
||||
IF HEADER[4] THEN%FILE ALREADY EXISTS 38506000
|
||||
BEGIN 38507000
|
||||
J:=WRITTENON OR HEADER[4].[11:1]; 38507500
|
||||
$ SET OMIT = SHAREDISK 38507799
|
||||
I~IF FIB[5].[1:1] OR NOT J THEN FIB[5].[13;3]~10 ELSE 38507800
|
||||
(HEADER INX 0)&FIB[5][30:13:3]; 38508000
|
||||
$ POP OMIT 38508001
|
||||
$ SET OMIT = NOT SHAREDISK 38508599
|
||||
IF(I~DIRECTORYSEARCH(MID,FID&J[3:47:1],I))!0 THEN 38509000
|
||||
IF PURGE THEN 38510000
|
||||
IF M[I+4].[12:4]=0 THEN 38511000
|
||||
IF NOT SYSTEMFILE(MID,FID) THEN 38512000
|
||||
IF SECURITYCHECK(MID,FID,USERCODE[P1MIX],I).[45:1] THEN 38513000
|
||||
P(DIRECTORYSEARCH(-MID,FID,7),DEL); 38515000
|
||||
IF I NEQ 0 THEN FORGETSPACE(I); 38516000
|
||||
END ELSE% 38517000
|
||||
IF NOT LOCK THEN% 38518000
|
||||
IF HEADER[4].[43:1] THEN P(DIRECTORYSEARCH(-MID,FID,7),DEL) ELSE 38518500
|
||||
BEGIN 38519000
|
||||
$ SET OMIT = NOT(DISKLOG) 38520000
|
||||
FOR I~10 STEP 1 UNTIL 29 DO% 38522000
|
||||
IF HEADER[I]!0 THEN FORGETUSERDISK(HEADER[I],-HEADER(8]);% 38523000
|
||||
END; 38524000
|
||||
FORGETSPACE(HEADER); 38525000
|
||||
STATE.[39:4]~1;% 38526000
|
||||
END; 38527000
|
||||
IF NOT COBOL THEN FIB[4].[27:3]~3; 38528000
|
||||
GO CLEANUP;% 38529000
|
||||
OBJTYPE::: "BASIC ", %1% 38530000
|
||||
"ALGOL ", %2% 38531000
|
||||
"COBOL ", %3% 38532000
|
||||
"FORTRAN", %4% 38533000
|
||||
"TSPOL ", %5% 38534000
|
||||
"XALGOL ", %6% 38535000
|
||||
0; %DUMMY% 38536000
|
||||
CLEANUP: 38537000
|
||||
P(P&RCW[CTC],0,RDS,0,XCH,P&P[CTF],STF); 38538000
|
||||
END DISK CLOSE; 38539000
|
||||
PROCEDURE BACKCLOSE(ALPHA); VALUE ALPHA; INTEGER ALPHA;% 38540000
|
||||
BEGIN REAL RCW=+0,MSCW=-2; 38541000
|
||||
ARRAY FIB=+1[*],FPB=+2[*],HEADER=+3[*];% 38542000
|
||||
DONT ADD ANY DECLARATIONS BETWEEN "HEADER" AND "KIND" %%% MCP 38543000
|
||||
INTEGER KIND=+4,NBUFS=+5,U=+6,BLEN=+7,CODE=+8, 38544000
|
||||
UNLABELED=+9,COBOL=+10,I=+11,J=+12, 38545000
|
||||
FNUM=+13; 38546000
|
||||
REAL MID=+14,FID=+15,R=+16,D=+17,C=+18,FORMS=+19,STATE=+20; 38547000
|
||||
LABEL AGAIN,EOF,EOT,CLOSEOUT,PBD,PUD; 38548000
|
||||
LABEL ZEROKDADDR; %175-38548010
|
||||
REAL STA=+21;% 38548100
|
||||
REAL T1=+22,T2=+23,T3=+24,IOD=+25;% 38549000
|
||||
ARRAY SEG0=+26[*],SKEL=+27[*]; 38550000
|
||||
SUBROUTINE COOLOFF;% 38552000
|
||||
BEGIN FOR I~0 STEP 1 UNTIL NBUFS-1 DO% 38553000
|
||||
BEGIN IF NOT M[ALPHA+I].[19:1] THEN% 38554000
|
||||
SLEEP([M[ALPHA+I]],IOMASK);% 38555000
|
||||
IF KIND!4 THEN 38556000
|
||||
IF M[ALPHA+I].[27:1] THEN GO TO EOF;% 38557000
|
||||
END;% 38558000
|
||||
EOF: END COOLOFF;% 38559000
|
||||
REAL T=+28,ACCESS=+29;% 38561000
|
||||
BOOLEAN COMPGO=+30; 38562000
|
||||
REAL TYPE=+31; 38562100
|
||||
DEFINE REW=CODE.[47:1]#,% 38563000
|
||||
REL=CODE.[46:1]#,% 38564000
|
||||
TIME=CODE.[45:1]#,% 38565000
|
||||
LOCK=NOT CODE.[44:1]#,% 38566000
|
||||
PURGE=NOT CODE.[43:1]#,% 38567000
|
||||
$ SET OMIT = PACKETS 38567950
|
||||
DEFINE TOREELNO = 42:42:6#; 38568100
|
||||
$ POP OMIT OMIT 38568150
|
||||
% 38569000
|
||||
SUBROUTINE CKBKUP; 38570000
|
||||
BEGIN M[M[ALPHA]INX 17 ]~M[ALPHA]&(FIB[5] )[FTC]; 38571000
|
||||
FIB[5]~P(DUP,LOD,0,1,CFX,+); 38572000
|
||||
IF NOT PRTROW[P1MIX].[7:1] THEN 38573000
|
||||
IF FIB[14].[CF]=FIB[14].[FF] 38573100
|
||||
THEN BEGIN PBIO(ALPHA,FIB[14]);SLEEP([M[ALPHA]],IOMASK)END ELSE38574000
|
||||
BEGIN; STREAM(S~ M[ALPHA],Z~FIB[14].[FF]); 38575000
|
||||
BEGIN SI~S; DS~18 WDS END; 38576000
|
||||
FIB[14].[FF]~P(DUP).[FF]-18; 38577000
|
||||
END; END; 38578000
|
||||
P(RCW,MSCW,STF); 38580000
|
||||
RCW:=RCW&P(XCH)[CTC]; 38581000
|
||||
J~LOCK; 38581100
|
||||
IF T1~(FIB[9].[1:1] AND KIND=7) THEN % MULTI-REEL PBT FILE 38581200
|
||||
BEGIN 38581300
|
||||
FIB[9].[1:1]~0; 38581400
|
||||
COOLOFF; 38581500
|
||||
GO TO EOT; 38581600
|
||||
END; 38581700
|
||||
IF M[FIB[14].[3:15] INX NOT 0] = 0 THEN % %175-38581800
|
||||
BEGIN %175-38581900
|
||||
I:=TYPE>20; % 1=PUD %175-38581910
|
||||
R:=M[FIB[14].[3:16]+6]; % PB REEL NO. %175-38581920
|
||||
GO TO ZERODKADDR; %175-38581930
|
||||
END; 38581940
|
||||
IF FIB[17]<0 THEN 38582000
|
||||
BEGIN M[ALPHA].[FF]~@60020; IF TYPE<20 THEN CKBKUP; 38583000
|
||||
M[ALPHA].[18:1]~0; CKBKUP END% 38584000
|
||||
ELSE IF FIB[17]<BLEN THEN% 38585000
|
||||
BEGIN IF NOT COBOL THEN FIB[17]~FIB[17]-(STATE.[46:2]=3);% 38586000
|
||||
STREAM(N:=FIB[17],D:=M[ALPHA].[CF]); 38587000
|
||||
BEGIN N(DS:=8 LIT " "); END; 38587500
|
||||
M[ALPHA]~FLAG(FIB[16]&0[20:47:1]); CKBKUP; 38588000
|
||||
END ELSE COOLOFF; 38589000
|
||||
M[ALPHA]~(*P(DUP))&(@60000)[CTF]; %150-38590000
|
||||
IF TYPE<20 THEN %150-38590100
|
||||
M[ALPHA]~(*P(DUP))&(IF SEPARATE THEN 1 ELSE @20)[27:42:6];%150-38590200
|
||||
IF NOT UNLABELED THEN 38591000
|
||||
BEGIN IF TYPE<20 THEN CKBKUP; 38591100
|
||||
M[ALPHA]~(*P(DUP))&2[18:45:3]&M[ALPHA-2][8:8:10]; 38592000
|
||||
IF NOT SEPARATE THEN M[ALPHA]~(*P(DUP))&1[27:42:6]; %150-38592100
|
||||
STREAM(L~M[ALPHA-2],B~M[ALPHA]); BEGIN SI~L; DS~17 WDS END; 38593000
|
||||
END; %150-38594000
|
||||
IF NOT SEPARATE THEN M[ALPHA]~P(DUP,LOD)&1[27:42:6]; %150-38594100
|
||||
M[ALPHA].[20:1]~1; %150-38594200
|
||||
IF FIB[14].[FF]!FIB[14].[CF] THEN %150-38594300
|
||||
BEGIN CKBKUP; FIB[14].[FF]~P(DUP);END; CKBKUP; 38595000
|
||||
IF KIND=12 THEN % PBD 38596000
|
||||
BEGIN T~FIB[14].[3:15]; 38597000
|
||||
IF (R:=M[T+7]|3) NEQ 0 THEN 38598000
|
||||
BEGIN IF R < PBDROWSZ THEN 38598100
|
||||
BEGIN FORGETUSERDISK(M[T+10]+R,PBDROWSZ-R+1); 38599000
|
||||
M[T+8]~R; 38600000
|
||||
END; 38601000
|
||||
END; %601-38601100
|
||||
M[T+1]~M[T+3]; 38602000
|
||||
STREAM(A~FPB[FNUM+2].[18:30],T~T+3); 38603000
|
||||
BEGIN SI~LOC A;DS~8 OCT;DI~DI-8;DS~2 LIT"+2"; 38604000
|
||||
SI~T;SI~SI+5;DS~3 CHR; 38605000
|
||||
END; 38606000
|
||||
M[T+1].[6:18]~M[T+3].[30:18]; 38607000
|
||||
IF I:=TYPE>20 THEN M[T+5].[3:1]:=0; 38607100
|
||||
R:=M[T+6]; 38607200
|
||||
$ SET OMIT = DATACOM AND RJE 38607299
|
||||
P(0); 38607300
|
||||
$ POP OMIT 38607301
|
||||
$ SET OMIT = NOT(DATACOM AND RJE ) 38607399
|
||||
M[T+6]:=P(XCH); 38607900
|
||||
M[T+5].[2:1]~0; 38607950
|
||||
DISKWAIT(T,30,M[T-1]); 38608000
|
||||
D:=R&1[TOREELNO]; 38608100
|
||||
AGAIN: P(DIRECTORYSEARCH(-(IF I THEN P(PUD) ELSE P(PBD)),D,14), 38608200
|
||||
DEL); 38608300
|
||||
IF D!R THEN 38608400
|
||||
BEGIN STREAM(ONE:=1, D:=[D]); 38608500
|
||||
BEGIN SI:=LOC ONE; DS:=8 ADD END; 38608600
|
||||
GO AGAIN; 38608700
|
||||
END; 38608800
|
||||
$ SET OMIT = NOT PACKETS 38608890
|
||||
IF (T1:=PSEUDOMIX[P1MIX])=0 THEN P(1) ELSE 38608900
|
||||
IF PACKETPAGE[T1-32]=0 THEN P(1) ELSE P(0); 38608910
|
||||
IF P OR 1 THEN %105-38608920
|
||||
BEGIN PBCOUNT:=PBCOUNT+1; 38609000
|
||||
$ POP OMIT 38609010
|
||||
|
||||
@@ -13,9 +13,12 @@
|
||||
"use strict";
|
||||
|
||||
/**************************************/
|
||||
function B5500CentralControl() {
|
||||
function B5500CentralControl(global) {
|
||||
/* Constructor for the Central Control module object */
|
||||
|
||||
this.mnemonic = "CC"; // Unit mnemonic
|
||||
this.global = global; // Javascript global object (e.g., "window" for browsers)
|
||||
|
||||
/* Global system modules */
|
||||
this.DD = null; // Distribution & Display unit
|
||||
this.PA = null; // Processor A (PA)
|
||||
@@ -61,11 +64,11 @@ function B5500CentralControl() {
|
||||
/**************************************/
|
||||
/* Global constants */
|
||||
|
||||
B5500CentralControl.version = "0.14";
|
||||
B5500CentralControl.version = "0.15";
|
||||
|
||||
B5500CentralControl.memReadCycles = 2; // assume 2 µs memory read cycle time (the other option was 3 µs)
|
||||
B5500CentralControl.memWriteCycles = 4; // assume 4 µs memory write cycle time (the other option was 6 µs)
|
||||
B5500CentralControl.rtcTick = 1000/60; // Real-time clock period, milliseconds
|
||||
B5500CentralControl.memReadCycles = 1.8; // assume 2 µs memory read cycle time (the other option was 3 µs)
|
||||
B5500CentralControl.memWriteCycles = 2.5; // assume 4 µs memory write cycle time (the other option was 6 µs)
|
||||
B5500CentralControl.rtcTick = 1000/60; // Real-time clock period, milliseconds
|
||||
|
||||
B5500CentralControl.pow2 = [ // powers of 2 from 0 to 52
|
||||
0x1, 0x2, 0x4, 0x8,
|
||||
@@ -117,37 +120,37 @@ B5500CentralControl.unitIndex = [
|
||||
// to the attributes needed to configure the CC unit[] array.
|
||||
|
||||
B5500CentralControl.unitSpecs = {
|
||||
SPO: {unitIndex: 22, designate: 30, unitClass: B5500SPOUnit},
|
||||
DKA: {unitIndex: 29, designate: 6, unitClass: B5500DiskUnit},
|
||||
DKB: {unitIndex: 28, designate: 12, unitClass: B5500DiskUnit},
|
||||
CRA: {unitIndex: 24, designate: 10, unitClass: B5500CardReader},
|
||||
CRB: {unitIndex: 23, designate: 14, unitClass: B5500CardReader},
|
||||
CPA: {unitIndex: 25, designate: 10, unitClass: B5500CardPunch},
|
||||
LPA: {unitIndex: 27, designate: 22, unitClass: B5500DummyPrinter},
|
||||
LPB: {unitIndex: 26, designate: 26, unitClass: B5500DummyPrinter},
|
||||
PRA: {unitIndex: 20, designate: 18, unitClass: null},
|
||||
PRB: {unitIndex: 19, designate: 20, unitClass: null},
|
||||
PPA: {unitIndex: 21, designate: 18, unitClass: null},
|
||||
DCA: {unitIndex: 17, designate: 16, unitClass: "B5500DatacomUnit"},
|
||||
PPB: {unitIndex: 18, designate: 20, unitClass: null},
|
||||
DCA: {unitIndex: 17, designate: 16, unitClass: null},
|
||||
DRA: {unitIndex: 31, designate: 4, unitClass: null},
|
||||
PRB: {unitIndex: 19, designate: 20, unitClass: null},
|
||||
PRA: {unitIndex: 20, designate: 18, unitClass: null},
|
||||
PPA: {unitIndex: 21, designate: 18, unitClass: null},
|
||||
SPO: {unitIndex: 22, designate: 30, unitClass: "B5500SPOUnit"},
|
||||
CRB: {unitIndex: 23, designate: 14, unitClass: "B5500CardReader"},
|
||||
CRA: {unitIndex: 24, designate: 10, unitClass: "B5500CardReader"},
|
||||
CPA: {unitIndex: 25, designate: 10, unitClass: "B5500CardPunch"},
|
||||
LPB: {unitIndex: 26, designate: 26, unitClass: "B5500DummyPrinter"},
|
||||
LPA: {unitIndex: 27, designate: 22, unitClass: "B5500DummyPrinter"},
|
||||
DKB: {unitIndex: 28, designate: 12, unitClass: "B5500DiskUnit"},
|
||||
DKA: {unitIndex: 29, designate: 6, unitClass: "B5500DiskUnit"},
|
||||
DRB: {unitIndex: 30, designate: 8, unitClass: null},
|
||||
MTA: {unitIndex: 47, designate: 1, unitClass: null},
|
||||
MTB: {unitIndex: 46, designate: 3, unitClass: null},
|
||||
MTC: {unitIndex: 45, designate: 5, unitClass: null},
|
||||
MTD: {unitIndex: 44, designate: 7, unitClass: null},
|
||||
MTE: {unitIndex: 43, designate: 9, unitClass: null},
|
||||
MTF: {unitIndex: 42, designate: 11, unitClass: null},
|
||||
MTH: {unitIndex: 41, designate: 13, unitClass: null},
|
||||
MTJ: {unitIndex: 40, designate: 15, unitClass: null},
|
||||
MTK: {unitIndex: 39, designate: 17, unitClass: null},
|
||||
MTL: {unitIndex: 38, designate: 19, unitClass: null},
|
||||
MTM: {unitIndex: 37, designate: 21, unitClass: null},
|
||||
MTN: {unitIndex: 36, designate: 23, unitClass: null},
|
||||
MTP: {unitIndex: 35, designate: 25, unitClass: null},
|
||||
MTR: {unitIndex: 34, designate: 27, unitClass: null},
|
||||
DRA: {unitIndex: 31, designate: 4, unitClass: null},
|
||||
MTT: {unitIndex: 32, designate: 31, unitClass: null},
|
||||
MTS: {unitIndex: 33, designate: 29, unitClass: null},
|
||||
MTT: {unitIndex: 32, designate: 31, unitClass: null}};
|
||||
MTR: {unitIndex: 34, designate: 27, unitClass: null},
|
||||
MTP: {unitIndex: 35, designate: 25, unitClass: null},
|
||||
MTN: {unitIndex: 36, designate: 23, unitClass: null},
|
||||
MTM: {unitIndex: 37, designate: 21, unitClass: null},
|
||||
MTL: {unitIndex: 38, designate: 19, unitClass: null},
|
||||
MTK: {unitIndex: 39, designate: 17, unitClass: null},
|
||||
MTJ: {unitIndex: 40, designate: 15, unitClass: null},
|
||||
MTH: {unitIndex: 41, designate: 13, unitClass: null},
|
||||
MTF: {unitIndex: 42, designate: 11, unitClass: null},
|
||||
MTE: {unitIndex: 43, designate: 9, unitClass: null},
|
||||
MTD: {unitIndex: 44, designate: 7, unitClass: null},
|
||||
MTC: {unitIndex: 45, designate: 5, unitClass: null},
|
||||
MTB: {unitIndex: 46, designate: 3, unitClass: null},
|
||||
MTA: {unitIndex: 47, designate: 1, unitClass: "B5500MagTapeDrive"}};
|
||||
|
||||
|
||||
/**************************************/
|
||||
@@ -588,8 +591,8 @@ B5500CentralControl.prototype.haltP2 = function haltP2() {
|
||||
|
||||
this.HP2F = 1;
|
||||
// We know P2 is not currently running on this thread, so save its registers
|
||||
if (this.P2 && this.P2BF) {
|
||||
this.P2.storeForInterrupt(0);
|
||||
if (this.P2 && this.P2.busy) {
|
||||
this.P2.storeForInterrupt(1, 0);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -600,7 +603,7 @@ B5500CentralControl.prototype.initiateP2 = function initiateP2() {
|
||||
interrupt. Otherwise, loads the INCW into P2's A register and initiates
|
||||
the processor. */
|
||||
|
||||
if (!this.P2 || this.P2BF) {
|
||||
if (this.P2BF || !this.P2) {
|
||||
this.CCI12F = 1; // set P2 busy interrupt
|
||||
this.signalInterrupt();
|
||||
} else {
|
||||
@@ -933,6 +936,12 @@ B5500CentralControl.prototype.configureSystem = function configureSystem() {
|
||||
cc.signalInterrupt();
|
||||
};
|
||||
break;
|
||||
case "DCA":
|
||||
return function signalDCA() {
|
||||
cc.CCI13F = 1;
|
||||
cc.signalInterrupt();
|
||||
};
|
||||
break;
|
||||
case "DKA":
|
||||
return function signalDKA() {
|
||||
cc.setUnitBusy(29, 0); // Is this needed here ??
|
||||
@@ -981,7 +990,7 @@ B5500CentralControl.prototype.configureSystem = function configureSystem() {
|
||||
if (cfg.units[mnem]) {
|
||||
specs = B5500CentralControl.unitSpecs[mnem];
|
||||
if (specs) {
|
||||
unitClass = specs.unitClass || B5500DummyUnit;
|
||||
unitClass = this.global[specs.unitClass || "B5500DummyUnit"];
|
||||
if (unitClass) {
|
||||
u = new unitClass(mnem, specs.unitIndex, specs.designate,
|
||||
makeChange(this, specs.unitIndex), makeSignal(this, mnem));
|
||||
@@ -1044,6 +1053,6 @@ B5500CentralControl.prototype.powerOff = function powerOff() {
|
||||
if (this.poweredUp) {
|
||||
this.halt();
|
||||
// Wait a little while for I/Os, etc., to finish
|
||||
setCallback(shutDown, this, 1000);
|
||||
setCallback(shutDown, this, 500);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
* [22:1] Direction bit (0=forward, 1=reverse for mag tape, 120/132 col for printers)
|
||||
* [23:1] Word count bit (0=ignore, 1=use word count in [8:10])
|
||||
* [24:1] I/O bit (0=write, 1=read)
|
||||
* [25:1] (not used by I/O Unit)
|
||||
* [25:1] Group-mark detected bit (used by DCCU)
|
||||
* [26:7] Control and error-reporting bits (depend on unit)
|
||||
* [33:15] Memory address
|
||||
*
|
||||
@@ -43,6 +43,7 @@ function B5500IOUnit(ioUnitID, cc) {
|
||||
/* Constructor for the I/O Unit object */
|
||||
|
||||
this.ioUnitID = ioUnitID; // I/O Unit ID ("1", "2", "3", or "4")
|
||||
this.mnemonic = "IO" + ioUnitID; // Unit mnemonic
|
||||
this.cc = cc; // Reference back to Central Control module
|
||||
|
||||
this.forkHandle = null; // Reference to current setCallback id
|
||||
@@ -62,11 +63,14 @@ function B5500IOUnit(ioUnitID, cc) {
|
||||
|
||||
// Establish contexts for asynchronously-called methods
|
||||
this.boundForkIO = B5500CentralControl.bindMethod(this.forkIO, this);
|
||||
this.boundFinishBusy = this.makeFinish(this.finishBusy);
|
||||
this.boundFinishDatacomRead = this.makeFinish(this.finishDatacomRead);
|
||||
this.boundFinishDatacomWrite = this.makeFinish(this.finishDatacomWrite);
|
||||
this.boundFinishDiskRead = this.makeFinish(this.finishDiskRead);
|
||||
this.boundFinishGeneric = this.makeFinish(this.finishGeneric);
|
||||
this.boundFinishGenericRead = this.makeFinish(this.finishGenericRead);
|
||||
this.boundFinishBusy = this.makeFinish(this.finishBusy);
|
||||
this.boundFinishDiskRead = this.makeFinish(this.finishDiskRead);
|
||||
this.boundFinishSPORead = this.makeFinish(this.finishSPORead);
|
||||
this.boundFinishTapeRead = this.makeFinish(this.finishTapeRead);
|
||||
|
||||
this.initiateStamp = 0; // Timestamp of last I/O initiation on this unit
|
||||
|
||||
@@ -136,7 +140,6 @@ B5500IOUnit.prototype.clear = function clear() {
|
||||
/* Initializes (and if necessary, creates) the I/O Unit state */
|
||||
|
||||
this.W = 0; // Memory buffer register
|
||||
this.D = 0; // I/O descriptor (control) register
|
||||
this.clearD(); // clear the D-register exploded fields
|
||||
|
||||
this.CC = 0; // Character counter (3 bits)
|
||||
@@ -190,13 +193,15 @@ B5500IOUnit.prototype.clearD = function clearD() {
|
||||
/* Clears the D-register and the exploded field variables used internally */
|
||||
|
||||
this.D = 0;
|
||||
this.Dunit = 0; // Unit designate field (5 bits)
|
||||
this.DwordCount = 0; // Word count field (10 bits)
|
||||
this.D02F = 0; // Set for some Mod III IOU results
|
||||
this.Dunit = 0; // Unit designate field [3:5]
|
||||
this.DwordCount = 0; // Word count field [8:10]
|
||||
this.D18F = 0; // Memory inhibit bit (0=transfer, 1=no transfer)
|
||||
this.D21F = 0; // Mode bit (0=alpha, 1=binary)
|
||||
this.D22F = 0; // Direction bit (0=forward), etc.
|
||||
this.D23F = 0; // Word counter bit (0=ignore, 1=use)
|
||||
this.D24F = 0; // I/O bit (0=write, 1=read)
|
||||
this.D25F = 0; // Group-mark detected (0=yes, 1=buffer exhausted)
|
||||
this.D26F = 0; // Memory address error bit
|
||||
this.D27F = 0; // Device error bit 1
|
||||
this.D28F = 0; // Device error bit 2
|
||||
@@ -380,8 +385,9 @@ B5500IOUnit.prototype.storeBuffer = function storeBuffer(chars, offset, mode, wo
|
||||
} else {
|
||||
c = table[buf[offset+(count++)]];
|
||||
w += c*power;
|
||||
power /= 64;
|
||||
if (++s > 7) {
|
||||
if (++s <= 7) {
|
||||
power /= 64;
|
||||
} else {
|
||||
this.W = w;
|
||||
if (overflow) {
|
||||
this.AOFF = 1; // for display only
|
||||
@@ -428,8 +434,8 @@ B5500IOUnit.prototype.storeBuffer = function storeBuffer(chars, offset, mode, wo
|
||||
|
||||
/**************************************/
|
||||
B5500IOUnit.prototype.storeBufferWithGM = function storeBufferWithGM(chars, offset, mode, words) {
|
||||
/* Converts characters in this.buffer from ANSI to BIC, assembles them into
|
||||
words, and stores the words into memory starting at this.Daddress.
|
||||
/* Converts characters in this.buffer from ANSI or BCLANSI to BIC, assembles
|
||||
them into words, and stores the words into memory starting at this.Daddress.
|
||||
"chars": the number of characters to store, starting at "offset" in the buffer;
|
||||
"mode": 0=BCLANSI, 1=ANSI; "words": maximum number of words to transfer.
|
||||
The final character stored from the buffer is followed in memory by a group-mark,
|
||||
@@ -455,8 +461,9 @@ B5500IOUnit.prototype.storeBufferWithGM = function storeBufferWithGM(chars, offs
|
||||
} else {
|
||||
c = table[buf[offset+(count++)]];
|
||||
w += c*power;
|
||||
power /= 64;
|
||||
if (++s > 7) {
|
||||
if (++s <= 7) {
|
||||
power /= 64;
|
||||
} else {
|
||||
this.W = w;
|
||||
if (overflow) {
|
||||
this.AOFF = 1; // for display only
|
||||
@@ -505,12 +512,172 @@ B5500IOUnit.prototype.storeBufferWithGM = function storeBufferWithGM(chars, offs
|
||||
return count;
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500IOUnit.prototype.storeBufferBackward = function storeBufferBackward(chars, offset, mode, words) {
|
||||
/* Converts characters in this.buffer from ANSI or BCLANSI to BIC, assembles
|
||||
them into words, and stores the words into memory in reverse order, starting
|
||||
at this.Daddress. Because this is an I/O done in the reverse direction, the
|
||||
starting memory address is at the end (high address) in memory. The driver stores
|
||||
characters in this.buffer in ascending sequence, however, so the first character
|
||||
in this.buffer will be the last (highest) one in memory.
|
||||
"chars": the number of characters to store, starting at "offset" in the buffer;
|
||||
"mode": 0=BCLANSI, 1=ANSI; "words": maximum number of words to transfer.
|
||||
At exit, updates this.Daddress with the final transfer address-1.
|
||||
This routine ignores this.D23F, and does NOT update this.wordCount.
|
||||
Returns the number of characters stored into memory from the buffer */
|
||||
var addr = this.Daddress; // local copy of memory address
|
||||
var buf = this.buffer; // local pointer to buffer
|
||||
var c; // current character code
|
||||
var count = 0; // number of characters fetched
|
||||
var done = (words == 0); // loop control
|
||||
var overflow = false; // memory address overflowed max
|
||||
var power = 1; // factor for character shifting into a word
|
||||
var s = 0; // character shift counter
|
||||
var table = (mode ? B5500IOUnit.ANSItoBIC : B5500IOUnit.BCLANSItoBIC);
|
||||
var w = 0; // local copy of this.W
|
||||
|
||||
while (!done) { // loop through the words
|
||||
if (count >= chars) {
|
||||
done = true;
|
||||
} else {
|
||||
c = table[buf[offset+(count++)]];
|
||||
w += c*power;
|
||||
if (++s <= 7) {
|
||||
power *= 64;
|
||||
} else {
|
||||
this.W = w;
|
||||
if (overflow) {
|
||||
this.AOFF = 1; // for display only
|
||||
this.D26F = 1; // address overflow: set invalid address error
|
||||
done = true;
|
||||
} else {
|
||||
this.store(addr); // store the word in memory
|
||||
}
|
||||
if (addr > 0) {
|
||||
addr--;
|
||||
} else {
|
||||
overflow = true;
|
||||
}
|
||||
w = s = 0;
|
||||
power = 1;
|
||||
if (--words <= 0) {
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // while !done
|
||||
|
||||
if (s > 0 && words > 0) { // partial word left to be stored
|
||||
while (++s <= 8) {
|
||||
w += (mode ? 0x00 : 0x0C)*power;
|
||||
power *= 64;
|
||||
}
|
||||
this.W = w;
|
||||
if (overflow) {
|
||||
this.AOFF = 1; // for display only
|
||||
this.D26F = 1; // address overflow: set invalid address error
|
||||
done = true;
|
||||
} else {
|
||||
this.store(addr); // store the word in memory
|
||||
}
|
||||
words--;
|
||||
if (addr > 0) {
|
||||
addr--;
|
||||
}
|
||||
}
|
||||
|
||||
this.Daddress = addr;
|
||||
return count;
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500IOUnit.prototype.storeBufferBackwardWithGM = function storeBufferBackwardWithGM(chars, offset, mode, words) {
|
||||
/* Converts characters in this.buffer from ANSI or BCLANSI to BIC, assembles
|
||||
them into words, and stores the words into memory in reverse order, starting
|
||||
at this.Daddress. Because this is an I/O done in the reverse direction, the
|
||||
starting memory address is at the end (high address) in memory. The driver stores
|
||||
characters in this.buffer in ascending sequence, however, so the first character
|
||||
in this.buffer will be the last (highest) one in memory.
|
||||
"chars": the number of characters to store, starting at "offset" in the buffer;
|
||||
"mode": 0=BCLANSI, 1=ANSI; "words": maximum number of words to transfer.
|
||||
The final character stored from the buffer is followed in memory by a group-mark,
|
||||
assuming the word count is not exhausted. At exit, updates this.Daddress with the
|
||||
final transfer address-1.
|
||||
This routine ignores this.D23F, and does NOT update this.wordCount.
|
||||
Returns the number of characters stored into memory from the buffer, plus one
|
||||
for the group-mark */
|
||||
var addr = this.Daddress; // local copy of memory address
|
||||
var buf = this.buffer; // local pointer to buffer
|
||||
var c; // current character code
|
||||
var count = 0; // number of characters fetched
|
||||
var done = (words == 0); // loop control
|
||||
var overflow = false; // memory address overflowed max
|
||||
var power = 1; // factor for character shifting into a word
|
||||
var s = 0; // character shift counter
|
||||
var table = (mode ? B5500IOUnit.ANSItoBIC : B5500IOUnit.BCLANSItoBIC);
|
||||
var w = 0; // local copy of this.W
|
||||
|
||||
while (!done) { // loop through the words
|
||||
if (count >= chars) {
|
||||
done = true;
|
||||
} else {
|
||||
c = table[buf[offset+(count++)]];
|
||||
w += c*power;
|
||||
if (++s <= 7) {
|
||||
power *= 64;
|
||||
} else {
|
||||
this.W = w;
|
||||
if (overflow) {
|
||||
this.AOFF = 1; // for display only
|
||||
this.D26F = 1; // address overflow: set invalid address error
|
||||
done = true;
|
||||
} else {
|
||||
this.store(addr); // store the word in memory
|
||||
}
|
||||
if (addr > 0) {
|
||||
addr--;
|
||||
} else {
|
||||
overflow = true;
|
||||
}
|
||||
w = s = 0;
|
||||
power = 1;
|
||||
if (--words <= 0) {
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // while !done
|
||||
|
||||
w += 0x1F*power; // set group mark in register
|
||||
s++;
|
||||
count++;
|
||||
|
||||
if (s > 0 && words > 0) { // partial word left to be stored
|
||||
this.W = w;
|
||||
if (overflow) {
|
||||
this.AOFF = 1; // for display only
|
||||
this.D26F = 1; // address overflow: set invalid address error
|
||||
done = true;
|
||||
} else {
|
||||
this.store(addr); // store the word in memory
|
||||
}
|
||||
words--;
|
||||
if (addr > 0) {
|
||||
addr--;
|
||||
}
|
||||
}
|
||||
|
||||
this.Daddress = addr;
|
||||
return count;
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500IOUnit.prototype.finish = function finish() {
|
||||
/* Called to finish an I/O operation on this I/O Unit. Constructs and stores
|
||||
the result descriptor, sets the appropriate I/O Finished interrupt in CC */
|
||||
|
||||
this.W = this.D =
|
||||
this.D02F * 0x200000000000 +
|
||||
this.Dunit * 0x10000000000 +
|
||||
this.DwordCount * 0x40000000 +
|
||||
this.D18F * 0x20000000 +
|
||||
@@ -518,6 +685,7 @@ B5500IOUnit.prototype.finish = function finish() {
|
||||
this.D22F * 0x2000000 +
|
||||
this.D23F * 0x1000000 +
|
||||
this.D24F * 0x800000 +
|
||||
this.D25F * 0x400000 +
|
||||
this.D26F * 0x200000 +
|
||||
this.D27F * 0x100000 +
|
||||
this.D28F * 0x80000 +
|
||||
@@ -561,8 +729,8 @@ B5500IOUnit.prototype.makeFinish = function makeFinish(f) {
|
||||
|
||||
/**************************************/
|
||||
B5500IOUnit.prototype.decodeErrorMask = function decodeErrorMask(errorMask) {
|
||||
/* Decodes the errorMask returned by the device drivers and ORs it into
|
||||
the D-register error bits */
|
||||
/* Decodes the common bits of the errorMask returned by the device drivers
|
||||
and ORs it into the D-register error bits */
|
||||
|
||||
if (errorMask & 0x01) {this.D32F = 1}
|
||||
if (errorMask & 0x02) {this.D31F = 1}
|
||||
@@ -602,22 +770,80 @@ B5500IOUnit.prototype.finishGenericRead = function finishGenericRead(errorMask,
|
||||
/* Handles a generic I/O finish when input data transfer, and optionally,
|
||||
word-count update, is needed. Note that this turns off the busyUnit mask bit in CC */
|
||||
|
||||
this.storeBuffer(length, 0, 1, (this.D23F ? this.DwordCount : 0x7FFF));
|
||||
this.storeBuffer(length, 0, 1, (this.D23F ? this.DwordCount : this.buffer.length));
|
||||
this.finishGeneric(errorMask, length);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500IOUnit.prototype.finishSPORead = function finishSPORead(errorMask, length) {
|
||||
/* Handles I/O finish for a SPO keyboard input operation */
|
||||
B5500IOUnit.prototype.finishDatacomRead = function finishDatacomRead(errorMask, length) {
|
||||
/* Handles I/O finish for a datacom read operation */
|
||||
var bufExhausted = (errorMask & 0x080) >>> 7;
|
||||
var tuBuf = (errorMask%0x10000000000 - errorMask%0x40000000)/0x40000000; // get TU/buf #
|
||||
|
||||
this.storeBufferWithGM(length, 0, 1, 0x7FFF);
|
||||
if (length > 0) {
|
||||
if (bufExhausted || (tuBuf & 0x10)) {
|
||||
this.storeBuffer(length, 0, 1, 56);
|
||||
} else {
|
||||
this.storeBufferWithGM(length, 0, 1, 56);
|
||||
}
|
||||
this.Daddress += (length+7) >>> 3;
|
||||
}
|
||||
// Decode the additional datacom status/error bits
|
||||
this.D25F = bufExhausted;
|
||||
this.D24F = (errorMask & 0x100) >>> 8;
|
||||
this.D23F = (errorMask & 0x200) >>> 9;
|
||||
this.DwordCount = tuBuf;
|
||||
this.finishGeneric(errorMask, length);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500IOUnit.prototype.finishDatacomWrite = function finishDatacomWrite(errorMask, length) {
|
||||
/* Handles I/O finish for a datacom write or interrogate operation */
|
||||
var bufExhausted = (errorMask & 0x080) >>> 7;
|
||||
var tuBuf = (errorMask%0x10000000000 - errorMask%0x40000000)/0x40000000; // get TU/buf #
|
||||
|
||||
if (length > 0) {
|
||||
this.Daddress += (length+7) >>> 3;
|
||||
}
|
||||
|
||||
// Decode the additional datacom status/error bits
|
||||
this.D25F = bufExhausted;
|
||||
this.D24F = (errorMask & 0x100) >>> 8;
|
||||
this.D23F = (errorMask & 0x200) >>> 9;
|
||||
this.DwordCount = tuBuf;
|
||||
this.finishGeneric(errorMask, length);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500IOUnit.prototype.initiateDatacomIO = function initiateDatacomIO(u) {
|
||||
/* Initiates an I/O to the B249 Data Transmission Control Unit (DTCU) and through
|
||||
to the B487 Data Transmission Terminal Unit (DTTU). Note that with datacom, the
|
||||
I/O lengths should be determined by the DTTU, but in this implementation they are
|
||||
determined by the IOUnit, so all of the requested lengths are in excess of the
|
||||
maximum DTTU buffer of 448 chars (56 B5500 words) */
|
||||
var chars;
|
||||
|
||||
this.D23F = 0; // datacom does not use word count field as a word count
|
||||
if (this.D24F) { // DCA read
|
||||
u.read(this.boundFinishDatacomRead, this.buffer, 449, this.D21F, this.DwordCount);
|
||||
} else if (this.D18F) { // DCA interrogate
|
||||
u.writeInterrogate(this.boundFinishDatacomWrite, this.DwordCount);
|
||||
} else { // DCA write
|
||||
if (this.DwordCount & 0x10) { // transparent (no GM) write
|
||||
chars = this.fetchBuffer(1, 57);
|
||||
} else { // GM-terminated write
|
||||
chars = this.fetchBufferWithGM(1, 57);
|
||||
}
|
||||
// Restore the starting memory address -- will be adjusted in finishDatacomWrite
|
||||
this.Daddress = this.D % 0x8000;
|
||||
u.write(this.boundFinishDatacomWrite, this.buffer, chars, this.D21F, this.DwordCount);
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500IOUnit.prototype.finishDiskRead = function finishDiskRead(errorMask, length) {
|
||||
/* Handles I/O finish for a DFCU data read operation */
|
||||
var segWords = Math.floor((length+7)/8);
|
||||
var segWords = (length+7) >>> 3;
|
||||
var memWords = (this.D23F ? this.DwordCount : segWords);
|
||||
|
||||
if (segWords < memWords) {
|
||||
@@ -725,6 +951,98 @@ B5500IOUnit.prototype.initiatePrinterIO = function initiatePrinterIO(u) {
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500IOUnit.prototype.finishSPORead = function finishSPORead(errorMask, length) {
|
||||
/* Handles I/O finish for a SPO keyboard input operation */
|
||||
|
||||
this.storeBufferWithGM(length, 0, 1, this.buffer.length);
|
||||
this.finishGeneric(errorMask, length);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500IOUnit.prototype.finishTapeRead = function finishDiskRead(errorMask, length) {
|
||||
/* Handles I/O finish for a tape drive read operation */
|
||||
var count;
|
||||
var memWords = (length+7) >>> 3;
|
||||
var partialCount = (errorMask % 0x040000) >>> 15;
|
||||
|
||||
if (this.D23F && memWords > this.DwordCount) {
|
||||
memWords = this.DwordCount;
|
||||
}
|
||||
|
||||
if (this.D22F) {
|
||||
if (this.D21F) {
|
||||
count = this.storeBufferBackward(length, 0, this.D21F, memWords);
|
||||
} else {
|
||||
count = this.storeBufferBackwardWithGM(length, 0, this.D21F, memWords);
|
||||
}
|
||||
} else {
|
||||
if (this.D21F) {
|
||||
count = this.storeBuffer(length, 0, this.D21F, memWords);
|
||||
} else {
|
||||
count = this.storeBufferWithGM(length, 0, this.D21F, memWords);
|
||||
}
|
||||
}
|
||||
|
||||
// For Mod III I/O Units, extra status bits can be reported in the word-count
|
||||
// field. The driver will report these in the higher-order bits of errorMask.
|
||||
// mask & 0x040000 => tape at EOT (becomes D14F)
|
||||
// mask & 0x080000 => tape at BOT (becomes D13F)
|
||||
// mask & 0x100000 => blank tape (becomes D12F)
|
||||
// mask & 0x200000 => reserved for relocated D29F memory parity bit (becomes D11F)
|
||||
// The original memory parity bit in D29F is relocated to D11F, and both
|
||||
// D02F and D29F are set unconditionally.
|
||||
|
||||
// In addition, the count of characters in a paritally-filled final word is
|
||||
// reported in the low-order three bits of the word count field. For a forward
|
||||
// read, this is the number of characters in the last partial word. For a back-
|
||||
// ward read, this is 7 minus the number of characters.
|
||||
|
||||
if ((errorMask & 0x3C0000) || this.D29F) {
|
||||
this.DwordCount = partialCount +
|
||||
((errorMask % 0x400000) >>> 18) * 0x08 +
|
||||
this.D29F * 0x40; // relocate the memory parity bit
|
||||
this.D02F = 1; // mark as a Mod III RD
|
||||
errorMask |= 0x08; // set the original mem parity bit unconditionally
|
||||
}
|
||||
|
||||
this.finishGeneric(errorMask, count);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500IOUnit.prototype.initiateTapeIO = function initiateTapeIO(u) {
|
||||
/* Initiates an I/O to a Magnetic Tape unit */
|
||||
var addr = this.Daddress; // initial data transfer address
|
||||
var chars; // characters to print
|
||||
var memWords; // words to fetch from memory
|
||||
|
||||
if (this.D24F) { // tape read operation
|
||||
if (this.D18F) { // memory inhibit -- maintenance commands
|
||||
this.D30F = 1; // (MAINTENANCE I/Os NOT YET IMPLEMENTED)
|
||||
this.finish();
|
||||
} else if (this.D23F && this.DwordCount == 0) { // forward or backward space
|
||||
u.space(this.boundFinishGeneric, 0, this.D22F);
|
||||
} else { // some sort of actual read
|
||||
memWords = (this.D23F ? this.DwordCount : this.buffer.length);
|
||||
u.read(this.boundFinishTapeRead, this.buffer, memWords*8, this.D21F, this.D22F);
|
||||
}
|
||||
} else { // tape write operation
|
||||
if (this.D23F && this.DwordCount == 0) {// interrogate drive status
|
||||
u.writeInterrogate(this.boundFinishGeneric, 0);
|
||||
} else if (this.D18F) { // memory inhibit
|
||||
if (this.D22F) { // backward write => rewind
|
||||
u.rewind(this.boundFinishGeneric);
|
||||
} else {
|
||||
this.D30F = 1; // (ERASE NOT YET IMPLEMENTED)
|
||||
this.finish();
|
||||
}
|
||||
} else {
|
||||
this.D30F = 1; // (ALL FORMS OF WRITE NOT YET IMPLEMENTED)
|
||||
this.finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500IOUnit.prototype.forkIO = function forkIO() {
|
||||
/* Asynchronously initiates an I/O operation on this I/O Unit for a peripheral device */
|
||||
@@ -742,10 +1060,10 @@ B5500IOUnit.prototype.forkIO = function forkIO() {
|
||||
this.D18F = (x >>> 29) & 1; // memory inhibit
|
||||
this.D21F = (x >>> 26) & 1; // mode
|
||||
this.D22F = (x >>> 25) & 1; // direction (for tapes)
|
||||
this.D23F = (x >>> 24) & 1; // use word counter
|
||||
this.D23F = (x >>> 24) & 1; // use word count
|
||||
this.D24F = (x >>> 23) & 1; // write/read
|
||||
this.LP = (x >>> 15) & 0x3F; // save control bits for disk, drum, and printer
|
||||
this.Daddress = x % 0x8000;
|
||||
this.Daddress = x % 0x8000; // starting memory address
|
||||
|
||||
this.busyUnit = index = B5500CentralControl.unitIndex[this.D24F & 1][this.Dunit & 0x1F];
|
||||
if (this.cc.testUnitBusy(index)) {
|
||||
@@ -774,7 +1092,7 @@ B5500IOUnit.prototype.forkIO = function forkIO() {
|
||||
|
||||
// datacom designate
|
||||
case 16:
|
||||
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
||||
this.initiateDatacomIO(u);
|
||||
break;
|
||||
|
||||
// card #1 reader/punch
|
||||
@@ -803,9 +1121,9 @@ B5500IOUnit.prototype.forkIO = function forkIO() {
|
||||
// SPO designate
|
||||
case 30:
|
||||
if (this.D24F) {
|
||||
u.read(this.boundFinishSPORead, this.buffer, 0x7FFF, 0, 0);
|
||||
u.read(this.boundFinishSPORead, this.buffer, this.buffer.length, 0, 0);
|
||||
} else {
|
||||
chars = this.fetchBufferWithGM(1, 0x7FFF);
|
||||
chars = this.fetchBufferWithGM(1, this.buffer.length);
|
||||
u.write(this.boundFinishGeneric, this.buffer, chars, 0, 0);
|
||||
}
|
||||
break;
|
||||
@@ -813,7 +1131,7 @@ B5500IOUnit.prototype.forkIO = function forkIO() {
|
||||
// magnetic tape designates
|
||||
case 1: case 3: case 5: case 7: case 9: case 11: case 13: case 15:
|
||||
case 17: case 19: case 21: case 23: case 25: case 27: case 29: case 31:
|
||||
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
||||
this.initiateTapeIO(u);
|
||||
break;
|
||||
|
||||
// drum designates
|
||||
|
||||
@@ -33,6 +33,7 @@ function B5500Processor(procID, cc) {
|
||||
/* Constructor for the Processor module object */
|
||||
|
||||
this.processorID = procID; // Processor ID ("A" or "B")
|
||||
this.mnemonic = "P" + procID; // Unit mnemonic
|
||||
this.cc = cc; // Reference back to Central Control module
|
||||
this.scheduler = null; // Reference to current setCallback token
|
||||
this.accessor = { // Memory access control block
|
||||
@@ -1190,10 +1191,10 @@ B5500Processor.prototype.streamOutputConvert = function streamOutputConvert(coun
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500Processor.prototype.storeForInterrupt = function storeForInterrupt(forTest) {
|
||||
B5500Processor.prototype.storeForInterrupt = function storeForInterrupt(forced, forTest) {
|
||||
/* Implements the 3011=SFI operator and the parts of 3411=SFT that are
|
||||
common to it. "forTest" implies use from SFT */
|
||||
var forced = this.Q & 0x40; // Q07F: Hardware-induced SFI syllable
|
||||
common to it. "forced" implies Q07F: a hardware-induced SFI syllable.
|
||||
"forTest" implies use from SFT */
|
||||
var saveAROF = this.AROF;
|
||||
var saveBROF = this.BROF;
|
||||
var temp;
|
||||
@@ -1271,12 +1272,6 @@ B5500Processor.prototype.storeForInterrupt = function storeForInterrupt(forTest)
|
||||
this.Y * 0x10000000 +
|
||||
(this.Q & 0x1FF) * 0x400000000 +
|
||||
0xC00000000000;
|
||||
if (forTest) {
|
||||
this.TM = 0;
|
||||
this.MROF = 0;
|
||||
this.MWOF = 0;
|
||||
}
|
||||
|
||||
this.M = this.R*64 + 8; // store initiate word at R+@10
|
||||
this.storeBviaM(); // [M] = B
|
||||
|
||||
@@ -1286,28 +1281,31 @@ B5500Processor.prototype.storeForInterrupt = function storeForInterrupt(forTest)
|
||||
this.SALF = 0;
|
||||
this.BROF = 0;
|
||||
this.AROF = 0;
|
||||
if (forced) {
|
||||
if (this.isP1) {
|
||||
this.T = 0x89; // inject 0211=ITI into T register
|
||||
if (forTest) {
|
||||
this.TM = 0;
|
||||
this.MROF = 0;
|
||||
this.MWOF = 0;
|
||||
}
|
||||
|
||||
if (forced || forTest) {
|
||||
this.CWMF = 0;
|
||||
}
|
||||
|
||||
if (!this.isP1) { // if it's P2
|
||||
this.stop(); // idle the P2 processor
|
||||
this.cc.P2BF = 0; // tell CC and P1 we've stopped
|
||||
} else { // otherwise, if it's P1
|
||||
if (!forTest) {
|
||||
this.T = 0x89; // inject 0211=ITI into P1's T register
|
||||
} else {
|
||||
this.stop(); // idle the processor
|
||||
this.cc.P2BF = 0; // tell P1 we've stopped
|
||||
}
|
||||
this.CWMF = 0;
|
||||
} else if (forTest) {
|
||||
this.CWMF = 0;
|
||||
if (this.isP1) {
|
||||
this.loadBviaM(); // B = [M]: load DD for test
|
||||
this.loadBviaM(); // B = [M]: load DD for test
|
||||
this.C = this.B % 0x8000;
|
||||
this.L = 0;
|
||||
this.PROF = 0; // require fetch at SECL
|
||||
this.PROF = 0; // require fetch at SECL
|
||||
this.G = 0;
|
||||
this.H = 0;
|
||||
this.K = 0;
|
||||
this.V = 0;
|
||||
} else {
|
||||
this.stop(); // idle the processor
|
||||
this.cc.P2BF = 0; // tell P1 we've stopped
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1369,6 +1367,8 @@ B5500Processor.prototype.initiate = function initiate(forTest) {
|
||||
|
||||
if (this.AROF) {
|
||||
this.B = bw = this.A;
|
||||
} else if (this.BROF) {
|
||||
bw = this.B;
|
||||
} else {
|
||||
this.adjustBFull();
|
||||
bw = this.B;
|
||||
@@ -1471,9 +1471,9 @@ B5500Processor.prototype.initiate = function initiate(forTest) {
|
||||
/**************************************/
|
||||
B5500Processor.prototype.initiateAsP2 = function initiateAsP2() {
|
||||
/* Called from Central Control to initiate the processor as P2. Fetches the
|
||||
INCW from @10 and calls initiate() */
|
||||
INCW from @10, injects an initiate P2 syllable into T, and calls start() */
|
||||
|
||||
this.NCSF = 0; // make sure P2 is in control state to execute the IP1 & access low mem
|
||||
this.NCSF = 0; // make sure P2 is in Control State to execute the IP1 & access low mem
|
||||
this.M = 0x08; // address of the INCW
|
||||
this.loadBviaM(); // B = [M]
|
||||
this.AROF = 0; // make sure A is invalid
|
||||
@@ -2014,7 +2014,7 @@ B5500Processor.prototype.integerDivide = function integerDivide() {
|
||||
// Now we step through the development of the quotient one octade at a time,
|
||||
// similar to that for DIV, but in addition to stopping when the high-order
|
||||
// octade of xx is non-zero (i.e., normalized), we can stop if the exponents
|
||||
// becomes equal. Since there is no rounding, we do not need to develop an
|
||||
// become equal. Since there is no rounding, we do not need to develop an
|
||||
// extra quotient digit.
|
||||
do {
|
||||
this.cycleCount += 3; // just estimate the average number of clocks
|
||||
@@ -2863,7 +2863,7 @@ B5500Processor.prototype.run = function run() {
|
||||
set up -- in particular there must be a syllable in T with TROF set, the
|
||||
current program word must be in P with PROF set, and the C & L registers
|
||||
must point to the next syllable to be executed.
|
||||
This routine will run while cycleCount < cycleLimit */
|
||||
This routine will continue to run while this.runCycles < this.cycleLimit */
|
||||
var noSECL = 0; // to support char mode dynamic count from CRF syllable
|
||||
var opcode; // copy of T register
|
||||
var t1; // scratch variable for internal instruction use
|
||||
@@ -2872,6 +2872,7 @@ B5500Processor.prototype.run = function run() {
|
||||
var t4; // ditto
|
||||
var variant; // high-order six bits of T register
|
||||
|
||||
this.runCycles = 0; // initialze the cycle counter for this time slice
|
||||
do {
|
||||
this.Q = 0;
|
||||
this.Y = 0;
|
||||
@@ -3018,16 +3019,15 @@ B5500Processor.prototype.run = function run() {
|
||||
case 0x14: // 2411: ZPI=Conditional Halt
|
||||
if (this.US14X) { // STOP OPERATOR switch on
|
||||
this.stop();
|
||||
this.cycleLimit = 0; // exit this.run()
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x18: // 3011: SFI=Store for Interrupt
|
||||
this.storeForInterrupt(0);
|
||||
this.storeForInterrupt(0, 0);
|
||||
break;
|
||||
|
||||
case 0x1C: // 3411: SFT=Store for Test
|
||||
this.storeForInterrupt(1);
|
||||
this.storeForInterrupt(0, 1);
|
||||
break;
|
||||
|
||||
default: // Anything else is a no-op
|
||||
@@ -3736,25 +3736,23 @@ B5500Processor.prototype.run = function run() {
|
||||
break;
|
||||
|
||||
case 0x12: // 2211: HP2=Halt Processor 2
|
||||
if (!this.NCSF) { // control-state only
|
||||
if (!(this.NCSF || this.cc.HP2F)) { // control-state only
|
||||
this.cc.haltP2();
|
||||
this.cycleLimit = 0; // give P2 a chance to clean up
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x14: // 2411: ZPI=Conditional Halt
|
||||
if (this.US14X) { // STOP OPERATOR switch on
|
||||
this.stop();
|
||||
this.cycleLimit = 0; // exit this.run()
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x18: // 3011: SFI=Store for Interrupt
|
||||
this.storeForInterrupt(0);
|
||||
this.storeForInterrupt(0, 0);
|
||||
break;
|
||||
|
||||
case 0x1C: // 3411: SFT=Store for Test
|
||||
this.storeForInterrupt(1);
|
||||
this.storeForInterrupt(0, 1);
|
||||
break;
|
||||
|
||||
case 0x21: // 4111: IP1=Initiate Processor 1
|
||||
@@ -3766,7 +3764,10 @@ B5500Processor.prototype.run = function run() {
|
||||
case 0x22: // 4211: IP2=Initiate Processor 2
|
||||
if (!this.NCSF) { // control-state only
|
||||
this.M = 0x08; // INCW is stored in @10
|
||||
if (this.BROF && !this.AROF) {
|
||||
if (this.AROF) {
|
||||
this.storeAviaM(); // [M] = A
|
||||
this.AROF = 0;
|
||||
} else if (this.BROF) {
|
||||
this.storeBviaM(); // [M] = B
|
||||
this.BROF = 0;
|
||||
} else {
|
||||
@@ -3782,7 +3783,10 @@ B5500Processor.prototype.run = function run() {
|
||||
case 0x24: // 4411: IIO=Initiate I/O
|
||||
if (!this.NCSF) {
|
||||
this.M = 0x08; // address of IOD is stored in @10
|
||||
if (this.BROF && !this.AROF) {
|
||||
if (this.AROF) {
|
||||
this.storeAviaM(); // [M] = A
|
||||
this.AROF = 0;
|
||||
} else if (this.BROF) {
|
||||
this.storeBviaM(); // [M] = B
|
||||
this.BROF = 0;
|
||||
} else {
|
||||
@@ -4536,10 +4540,10 @@ B5500Processor.prototype.run = function run() {
|
||||
|
||||
if ((this.isP1 ? this.cc.IAR : this.I) && this.NCSF) {
|
||||
// there's an interrupt and we're in normal state
|
||||
// reset Q09F (R-relative adder mode) and set Q07F (hardware-induced SFI) (for display only)
|
||||
this.Q = (this.Q & 0xFFFEFF) & 0x40;
|
||||
this.T = 0x0609; // inject 3011=SFI into T
|
||||
this.Q &= 0xFFFEFF; // reset Q09F: adder mode for R-relative addressing
|
||||
this.Q |= 0x40; // set Q07F to indicate hardware-induced SFI
|
||||
this.storeForInterrupt(0); // call directly to avoid resetting registers at top of loop
|
||||
this.storeForInterrupt(1, 0); // call directly to avoid resetting registers at top of loop
|
||||
} else {
|
||||
// otherwise, fetch the next instruction
|
||||
if (!this.PROF) {
|
||||
@@ -4567,7 +4571,10 @@ B5500Processor.prototype.run = function run() {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.NCSF) {
|
||||
// Accumulate Normal and Control State cycles for use by the Console in
|
||||
// making the pretty lights blink. If the processor is no longer busy,
|
||||
// accumulate the cycles as Normal State, as we probably just did SFI.
|
||||
if (this.NCSF || !this.busy) {
|
||||
this.normalCycles += this.cycleCount;
|
||||
} else {
|
||||
this.controlCycles += this.cycleCount;
|
||||
@@ -4599,7 +4606,6 @@ B5500Processor.prototype.schedule = function schedule() {
|
||||
|
||||
if (this.busy) {
|
||||
this.cycleLimit = B5500Processor.timeSlice;
|
||||
this.runCycles = 0;
|
||||
|
||||
this.run(); // execute syllables for the timeslice
|
||||
|
||||
@@ -4619,6 +4625,9 @@ B5500Processor.prototype.schedule = function schedule() {
|
||||
// delay is less than our estimate of that minimum, we yield to the event loop
|
||||
// but otherwise continue (real time should eventually catch up -- we hope). If the
|
||||
// delay is greater than the minimum, we reschedule ourselves after that delay.
|
||||
if (delayTime < this.delayDeltaAvg) {
|
||||
delayTime = 0;
|
||||
}
|
||||
|
||||
this.delayRequested = delayTime;
|
||||
this.scheduler = setCallback(this.schedule, this, delayTime);
|
||||
@@ -4633,7 +4642,6 @@ B5500Processor.prototype.step = function step() {
|
||||
or two injected instructions (e.g., SFI followed by ITI) could also be executed */
|
||||
|
||||
this.cycleLimit = 1;
|
||||
this.runCycles = 0;
|
||||
|
||||
this.run();
|
||||
|
||||
|
||||
@@ -50,10 +50,10 @@ var B5500SystemConfiguration = {
|
||||
PRB: false, // Paper Tape Reader B
|
||||
PPA: false, // Paper Tape Punch A
|
||||
PPB: false, // Paper Tape Punch A
|
||||
DCA: false, // Data Communications Control A
|
||||
DCA: true, // Data Communications Control A
|
||||
DRA: false, // Drum/Auxmem A
|
||||
DRB: false, // Drum/Auxmem B
|
||||
MTA: false, // Magnetic Tape Unit A
|
||||
MTA: true, // Magnetic Tape Unit A
|
||||
MTB: false, // Magnetic Tape Unit B
|
||||
MTC: false, // Magnetic Tape Unit C
|
||||
MTD: false, // Magnetic Tape Unit D
|
||||
|
||||
@@ -167,6 +167,19 @@ window.addEventListener("load", function() {
|
||||
return m[1];
|
||||
}
|
||||
|
||||
/***************************************/
|
||||
function padOctal(value, octades) {
|
||||
/* Formats "value" as an octal number of "octades" length, left-padding with
|
||||
zeroes as necessary */
|
||||
var text = value.toString(8);
|
||||
var len = text.length;
|
||||
|
||||
while (len++ < octades) {
|
||||
text = "0" + text;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**************************************/
|
||||
function padToLength(text, len) {
|
||||
/* Converts the input string "text" to exactly "len" characters,
|
||||
@@ -384,8 +397,9 @@ window.addEventListener("load", function() {
|
||||
}
|
||||
|
||||
/**************************************/
|
||||
function formatDirectoryEntry(body, mfid, fid, header) {
|
||||
function formatDirectoryEntry(body, mfid, fid, headerBlock) {
|
||||
/* Formats the disk header for a file */
|
||||
var header = readDiskHeader(headerBlock);
|
||||
var row = document.createElement("tr");
|
||||
var rx;
|
||||
var text = "";
|
||||
@@ -419,7 +433,13 @@ window.addEventListener("load", function() {
|
||||
for (rx=0; rx<header.maxRows; rx++) {
|
||||
text += header.rowAddress[rx].toFixed(0) + " ";
|
||||
}
|
||||
appendCell(row, text);
|
||||
body.appendChild(row);
|
||||
|
||||
text = "";
|
||||
for (rx=0; rx<10; rx++) {
|
||||
text += padOctal(headerBlock[rx], 16) + " ";
|
||||
}
|
||||
appendCell(row, text);
|
||||
body.appendChild(row);
|
||||
}
|
||||
@@ -437,6 +457,7 @@ window.addEventListener("load", function() {
|
||||
var atEnd = false;
|
||||
var bx;
|
||||
var fid;
|
||||
var headerBlock;
|
||||
var mfid;
|
||||
var namex;
|
||||
|
||||
@@ -451,7 +472,7 @@ window.addEventListener("load", function() {
|
||||
mfid = wordsToString(block, bx, 1, true);
|
||||
fid = wordsToString(block, bx+1, 1, true);
|
||||
bx = namex*30;
|
||||
formatDirectoryEntry(body, mfid, fid, readDiskHeader(block.slice(bx, bx+30)));
|
||||
formatDirectoryEntry(body, mfid, fid, block.slice(bx, bx+30));
|
||||
}
|
||||
}
|
||||
if (atEnd) {
|
||||
@@ -559,6 +580,8 @@ TABLE {
|
||||
border-collapse: collapse}
|
||||
TH {
|
||||
vertical-align: bottom}
|
||||
TD {
|
||||
white-space: nowrap}
|
||||
.center {
|
||||
text-align: center}
|
||||
.rj {
|
||||
@@ -596,6 +619,7 @@ TH {
|
||||
<th>SPR
|
||||
<th>MXR
|
||||
<th>Row Addresses
|
||||
<th>Raw Header Words 0-9
|
||||
<tbody id=DirListBody>
|
||||
</table>
|
||||
|
||||
|
||||
140
tools/BCD-Build-Xlate.html
Normal file
140
tools/BCD-Build-Xlate.html
Normal file
@@ -0,0 +1,140 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Build .bdc Tape Image Translate Tables</title>
|
||||
<style>
|
||||
TD {
|
||||
font-family: Lucida Sans Typewriter, Courier New, Courier, monospace;
|
||||
font-size: smaller;
|
||||
text-align: right}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
BICtoANSI = [ // Index by 6-bit BIC to get 8-bit ANSI code
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", // 00-07, @00-07
|
||||
"8", "9", "#", "@", "?", ":", ">", "}", // 08-0F, @10-17
|
||||
"+", "A", "B", "C", "D", "E", "F", "G", // 10-17, @20-27
|
||||
"H", "I", ".", "[", "&", "(", "<", "~", // 18-1F, @30-37
|
||||
"|", "J", "K", "L", "M", "N", "O", "P", // 20-27, @40-47
|
||||
"Q", "R", "$", "*", "-", ")", ";", "{", // 28-2F, @50-57
|
||||
" ", "/", "S", "T", "U", "V", "W", "X", // 30-37, @60-67
|
||||
"Y", "Z", ",", "%", "!", "=", "]", "\""]; // 38-3F, @70-77
|
||||
|
||||
BICtoBCLANSI = [ // Index by 6-bit BIC to get 8-bit BCL-as-ANSI code
|
||||
"#", "1", "2", "3", "4", "5", "6", "7", // 00-07, @00-07
|
||||
"8", "9", "@", "?", "0", ":", ">", "}", // 08-1F, @10-17
|
||||
",", "/", "S", "T", "U", "V", "W", "X", // 10-17, @20-27
|
||||
"Y", "Z", "%", "!", " ", "=", "]", "\"", // 18-1F, @30-37
|
||||
"$", "J", "K", "L", "M", "N", "O", "P", // 20-27, @40-47
|
||||
"Q", "R", "*", "-", "|", ")", ";", "{", // 28-2F, @50-57
|
||||
"+", "A", "B", "C", "D", "E", "F", "G", // 30-37, @60-67
|
||||
"H", "I", "[", "&", ".", "(", "<", "~"]; // 38-3F, @70-77
|
||||
|
||||
BCLtoANSI = [ // Index by 6-bit BCL to get 8-bit ANSI code
|
||||
"?", "1", "2", "3", "4", "5", "6", "7", // 00-07, @00-07
|
||||
"8", "9", "0", "#", "@", ":", ">", "}", // 08-0F, @10-17
|
||||
" ", "/", "S", "T", "U", "V", "W", "X", // 10-17, @20-27
|
||||
"Y", "Z", "!", ",", "%", "=", "]", "\"", // 18-1F, @30-37
|
||||
"-", "J", "K", "L", "M", "N", "O", "P", // 20-27, @40-47
|
||||
"Q", "R", "|", "$", "*", ")", ";", "{", // 28-2F, @50-57
|
||||
"&", "A", "B", "C", "D", "E", "F", "G", // 30-37, @60-67
|
||||
"H", "I", "+", ".", "[", "(", "<", "~"]; // 38-3F, @70-77
|
||||
|
||||
function html(c) {
|
||||
switch(c) {
|
||||
case "<": return "<";
|
||||
case ">": return ">";
|
||||
case '"': return """;
|
||||
case "&": return "&";
|
||||
case " ": return " "
|
||||
default: return c;
|
||||
}
|
||||
}
|
||||
|
||||
function parity(c) {
|
||||
return ((c & 0x01) ^ ((c & 0x02) >>> 1) ^ ((c & 0x04) >>> 2) ^
|
||||
((c & 0x08) >>> 3) ^ ((c & 0x10) >>> 4) ^ ((c & 0x20) >>> 5));
|
||||
|
||||
}
|
||||
|
||||
function pic9n(v, n) {
|
||||
var text = v.toString();
|
||||
|
||||
if (text.length > n) {
|
||||
text = text.substring(-n);
|
||||
} else {
|
||||
while (text.length < n) {
|
||||
text = "0" + text;
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
var bcdEven = new Array(128);
|
||||
var bcdOdd = new Array(128);
|
||||
|
||||
var c;
|
||||
var even;
|
||||
var odd;
|
||||
var x;
|
||||
var y;
|
||||
|
||||
document.write("<h3>Build .bdc Tape Image Translate Tables</h3>");
|
||||
|
||||
// Initialize the tables with invalid parity codes
|
||||
for (x=0; x<128; x++) {
|
||||
bcdEven[x] = bcdOdd[x] = 0xFF;
|
||||
}
|
||||
|
||||
// Build the even/odd parity codes
|
||||
document.write("<table border=1 cellspacing=0><thead><tr><th>Code<th>P<th>BIC<th>Odd<th>BCL<th>Even<tbody>");
|
||||
for (c=0; c<64; c++) {
|
||||
// Odd parity translates
|
||||
odd = 1-parity(c);
|
||||
x = c | (odd << 6);
|
||||
bcdOdd[x] = BICtoANSI[c].charCodeAt(0);
|
||||
|
||||
// Even parity translates
|
||||
even = parity(c);
|
||||
y = c | (even << 6);
|
||||
bcdEven[y] = BICtoANSI[c].charCodeAt(0);
|
||||
|
||||
document.write("<tr><td>");
|
||||
document.write(pic9n(c.toString(8), 2));
|
||||
document.write("<td>");
|
||||
document.write(odd.toString(2));
|
||||
document.write("<td>");
|
||||
document.write(html(BICtoANSI[c]));
|
||||
document.write("<td>");
|
||||
document.write(pic9n(x.toString(2), 7));
|
||||
document.write("<td>");
|
||||
document.write(html(BCLtoANSI[c]));
|
||||
document.write("<td>");
|
||||
document.write(pic9n(y.toString(2), 7));
|
||||
}
|
||||
document.write("</table>");
|
||||
|
||||
document.write("<h3>Odd Parity Table</h3><code>");
|
||||
|
||||
for (x=0; x<128; x++) {
|
||||
document.write("0x" + bcdOdd[x].toString(16).toUpperCase() + ", ");
|
||||
if (x % 16 == 15) {
|
||||
document.write("<br>");
|
||||
}
|
||||
}
|
||||
document.write("</code>");
|
||||
|
||||
document.write("<h3>Even Parity Table</h3><code>");
|
||||
|
||||
for (x=0; x<128; x++) {
|
||||
document.write("0x" + bcdEven[x].toString(16).toUpperCase() + ", ");
|
||||
if (x % 16 == 15) {
|
||||
document.write("<br>");
|
||||
}
|
||||
}
|
||||
document.write("</code>");
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,10 +1,9 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Test BIC to ANSI Translation</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
var c;
|
||||
var d;
|
||||
var x;
|
||||
var y;
|
||||
|
||||
BICtoANSI = [ // Index by 6-bit BIC to get 8-bit ANSI code
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", // 00-07, @00-07
|
||||
@@ -25,7 +24,7 @@ BICtoBCLANSI = [ // Index by 6-bit BIC to get 8-bit BCL-as-ANSI code
|
||||
"Q", "R", "*", "-", "|", ")", ";", "{", // 28-2F, @50-57
|
||||
"+", "A", "B", "C", "D", "E", "F", "G", // 30-37, @60-67
|
||||
"H", "I", "[", "&", ".", "(", "<", "~"]; // 38-3F, @70-77
|
||||
|
||||
|
||||
ANSItoBIC = [ // Index by 8-bit ANSI to get 6-bit BIC (upcased, invalid=>"?")
|
||||
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 00-0F
|
||||
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 10-1F
|
||||
@@ -43,7 +42,7 @@ ANSItoBIC = [ // Index by 8-bit ANSI to get 6-bit BIC (upcased, in
|
||||
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // D0-DF
|
||||
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // E0-EF
|
||||
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C]; // F0-FF
|
||||
|
||||
|
||||
BCLANSItoBIC = [ // Index by 8-bit BCL-as-ANSI to get 6-bit BIC (upcased, invalid=>"?")
|
||||
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 00-0F
|
||||
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 10-1F
|
||||
@@ -61,7 +60,7 @@ BCLANSItoBIC = [ // Index by 8-bit BCL-as-ANSI to get 6-bit BIC (upca
|
||||
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // D0-DF
|
||||
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // E0-EF
|
||||
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C]; // F0-FF
|
||||
|
||||
|
||||
function html(c) {
|
||||
switch(c) {
|
||||
case "<": return("<");
|
||||
@@ -72,7 +71,12 @@ function html(c) {
|
||||
}
|
||||
}
|
||||
|
||||
document.write("<h3>Test BIC to ANSI</h3>");
|
||||
var c;
|
||||
var d;
|
||||
var x;
|
||||
var y;
|
||||
|
||||
document.write("<h3>Test BIC to ANSI Translation</h3>");
|
||||
|
||||
for (x=0; x<64; x++) {
|
||||
c = BICtoANSI[x];
|
||||
|
||||
@@ -37,7 +37,7 @@ BUTTON.greenButton {
|
||||
color: white;
|
||||
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
|
||||
font-size: 8pt;
|
||||
font-weight: bold;
|
||||
font-weight: normal;
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: 1px solid #DDD;
|
||||
@@ -48,7 +48,7 @@ BUTTON.blackButton {
|
||||
color: white;
|
||||
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
|
||||
font-size: 8pt;
|
||||
font-weight: bold;
|
||||
font-weight: normal;
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: 1px solid #DDD;
|
||||
@@ -59,7 +59,7 @@ BUTTON.redButton {
|
||||
color: white;
|
||||
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
|
||||
font-size: 8pt;
|
||||
font-weight: bold;
|
||||
font-weight: normal;
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: 1px solid #DDD;
|
||||
|
||||
@@ -41,7 +41,7 @@ function B5500CardPunch(mnemonic, unitIndex, designate, statusChange, signal) {
|
||||
this.endOfStacker1 = null;
|
||||
this.stacker2 = null;
|
||||
this.endOfStacker2 = null;
|
||||
this.window = window.open("/B5500/webUI/B5500CardPunch.html", mnemonic,
|
||||
this.window = window.open("../webUI/B5500CardPunch.html", mnemonic,
|
||||
"scrollbars=no,resizable,width=560,height=204,left=0,top=220");
|
||||
this.window.addEventListener("load", function windowLoad() {
|
||||
that.punchOnload();
|
||||
@@ -49,7 +49,7 @@ function B5500CardPunch(mnemonic, unitIndex, designate, statusChange, signal) {
|
||||
}
|
||||
|
||||
B5500CardPunch.prototype.cardsPerMinute = 300; // Punch speed
|
||||
B5500CardPunch.prototype.maxScrollLines = 800; // Maximum punch stacker scrollback (stacker capacity)
|
||||
B5500CardPunch.prototype.maxScrollLines = 850; // Maximum punch stacker scrollback (stacker capacity)
|
||||
|
||||
/**************************************/
|
||||
B5500CardPunch.prototype.$$ = function $$(e) {
|
||||
|
||||
@@ -30,7 +30,7 @@ BUTTON.greenButton {
|
||||
color: white;
|
||||
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
|
||||
font-size: 8pt;
|
||||
font-weight: bold;
|
||||
font-weight: normal;
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: 1px solid #DDD;
|
||||
@@ -41,7 +41,7 @@ BUTTON.blackButton {
|
||||
color: white;
|
||||
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
|
||||
font-size: 8pt;
|
||||
font-weight: bold;
|
||||
font-weight: normal;
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: 1px solid #DDD;
|
||||
@@ -52,7 +52,7 @@ BUTTON.redButton {
|
||||
color: white;
|
||||
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
|
||||
font-size: 8pt;
|
||||
font-weight: bold;
|
||||
font-weight: normal;
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: 1px solid #DDD;
|
||||
|
||||
@@ -38,12 +38,13 @@ function B5500CardReader(mnemonic, unitIndex, designate, statusChange, signal) {
|
||||
this.window = null;
|
||||
}
|
||||
this.doc = null;
|
||||
this.window = window.open("/B5500/webUI/B5500CardReader.html", mnemonic,
|
||||
this.window = window.open("../webUI/B5500CardReader.html", mnemonic,
|
||||
"scrollbars=no,resizable,width=560,height=160,left="+x+",top="+x);
|
||||
this.window.addEventListener("load", function windowLoad() {
|
||||
that.readerOnload();
|
||||
}, false);
|
||||
|
||||
this.progressBar = null;
|
||||
this.outHopperFrame = null;
|
||||
this.outHopper = null;
|
||||
}
|
||||
@@ -62,11 +63,6 @@ B5500CardReader.prototype.cardFilter = [ // Filter ASCII character values to val
|
||||
0x3F,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, // 60-6F
|
||||
0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x7B,0x7C,0x7D,0x7E,0x3F]; // 70-7F
|
||||
|
||||
/**************************************/
|
||||
B5500CardReader.prototype.$$ = function $$(e) {
|
||||
return this.doc.getElementById(e);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500CardReader.prototype.clear = function clear() {
|
||||
/* Initializes (and if necessary, creates) the reader unit state */
|
||||
@@ -83,6 +79,11 @@ B5500CardReader.prototype.clear = function clear() {
|
||||
this.eofArmed = false; // EOF button: armed state
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500CardReader.prototype.$$ = function $$(e) {
|
||||
return this.doc.getElementById(e);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500CardReader.prototype.hasClass = function hasClass(e, name) {
|
||||
/* returns true if element "e" has class "name" in its class list */
|
||||
@@ -182,7 +183,7 @@ B5500CardReader.prototype.CRProgressBar_onclick = function CRProgressBar_onclick
|
||||
this.buffer = "";
|
||||
this.bufLength = 0;
|
||||
this.bufIndex = 0;
|
||||
this.$$("CRProgressBar").value = 0;
|
||||
this.progressBar.value = 0;
|
||||
while (this.outHopper.childNodes.length > 0) {
|
||||
this.outHopper.removeChild(this.outHopper.firstChild);
|
||||
}
|
||||
@@ -326,6 +327,7 @@ B5500CardReader.prototype.readerOnload = function readerOnload() {
|
||||
this.doc = this.window.document;
|
||||
this.doc.title = "retro-B5500 " + this.mnemonic;
|
||||
|
||||
this.progressBar = this.$$("CRProgressBar");
|
||||
this.outHopperFrame = this.$$("CROutHopperFrame");
|
||||
this.outHopperFrame.contentDocument.head.innerHTML += "<style>" +
|
||||
"BODY {background-color: white; margin: 2px} " +
|
||||
@@ -355,7 +357,7 @@ B5500CardReader.prototype.readerOnload = function readerOnload() {
|
||||
that.CREOFBtn_onclick(ev);
|
||||
}, false);
|
||||
|
||||
this.$$("CRProgressBar").addEventListener("click", function progressClick(ev) {
|
||||
this.progressBar.addEventListener("click", function progressClick(ev) {
|
||||
that.CRProgressBar_onclick(ev);
|
||||
}, false);
|
||||
};
|
||||
@@ -385,10 +387,16 @@ B5500CardReader.prototype.read = function read(finish, buffer, length, mode, con
|
||||
} else {
|
||||
card = this.readCardBinary(buffer, length);
|
||||
}
|
||||
|
||||
this.timer = setTimeout(function readDelay() {
|
||||
that.busy = false;
|
||||
finish(that.errorMask, length);
|
||||
}, 60000/this.cardsPerMinute + this.initiateStamp - new Date().getTime());
|
||||
|
||||
if (this.bufIndex < this.bufLength) {
|
||||
this.$$("CRProgressBar").value = this.bufLength-this.bufIndex;
|
||||
this.progressBar.value = this.bufLength-this.bufIndex;
|
||||
} else {
|
||||
this.$$("CRProgressBar").value = 0;
|
||||
this.progressBar.value = 0;
|
||||
this.buffer = ""; // discard the input buffer
|
||||
this.bufLength = 0;
|
||||
this.bufIndex = 0;
|
||||
@@ -396,11 +404,6 @@ B5500CardReader.prototype.read = function read(finish, buffer, length, mode, con
|
||||
this.$$("CRFileSelector").value = null; // reset the control so the same file can be reloaded
|
||||
}
|
||||
|
||||
this.timer = setTimeout(function readDelay() {
|
||||
that.busy = false;
|
||||
finish(that.errorMask, length);
|
||||
}, 60000/this.cardsPerMinute + this.initiateStamp - new Date().getTime());
|
||||
|
||||
while (this.outHopper.childNodes.length > 1) {
|
||||
this.outHopper.removeChild(this.outHopper.firstChild);
|
||||
}
|
||||
|
||||
@@ -533,7 +533,7 @@ window.addEventListener("load", function() {
|
||||
} else {
|
||||
c = tapeMark; // to kill the loop
|
||||
}
|
||||
} while (c < 128);
|
||||
} while (c < 0x80);
|
||||
ctl.eof = false;
|
||||
ctl.blockLength = x - ctl.offset;
|
||||
ctl.offset = x;
|
||||
@@ -584,7 +584,7 @@ window.addEventListener("load", function() {
|
||||
} else {
|
||||
c = tapeMark; // to kill the loop
|
||||
}
|
||||
} while (c < 128);
|
||||
} while (c < 0x80);
|
||||
|
||||
// Right-justify the last word as necessary
|
||||
while (wx++ < 8) {
|
||||
@@ -1193,6 +1193,23 @@ window.addEventListener("load", function() {
|
||||
var text = "";
|
||||
var x = 0;
|
||||
|
||||
function resetSelAll(ev) {
|
||||
$$("SelAll").checked = false;
|
||||
}
|
||||
|
||||
function selectAll(ev) {
|
||||
var checkboxes = body.getElementsByTagName("input");
|
||||
var e;
|
||||
var x;
|
||||
|
||||
for (x=0; x<checkboxes.length; x++) {
|
||||
e = checkboxes[x];
|
||||
if (e.type == "checkbox" && e.id != "SelAll") {
|
||||
e.checked = ev.target.checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clearPanel();
|
||||
while (body.firstChild) {
|
||||
body.removeChild(body.firstChild);
|
||||
@@ -1211,13 +1228,19 @@ window.addEventListener("load", function() {
|
||||
tapeDir = readTapeDirectory(tapeCtl);
|
||||
|
||||
row = document.createElement("tr");
|
||||
// File number and checkbox (empty)
|
||||
// Checkbox, File number, and File name
|
||||
cell = document.createElement("td");
|
||||
cell.colSpan=2;
|
||||
row.appendChild(cell);
|
||||
// File name
|
||||
cell = document.createElement("td");
|
||||
cell.appendChild(document.createTextNode("(none)"));
|
||||
cell.colSpan=3;
|
||||
e = document.createElement("input");
|
||||
e.type = "checkbox";
|
||||
e.id = "SelAll";
|
||||
e.value = "SelAll";
|
||||
e.addEventListener("click", selectAll);
|
||||
cell.appendChild(e);
|
||||
e = document.createElement("label");
|
||||
e.appendChild(document.createTextNode("Select All"));
|
||||
e.htmlFor = "SelAll";
|
||||
cell.appendChild(e);
|
||||
row.appendChild(cell);
|
||||
// Load as MCP for no selection
|
||||
cell = document.createElement("td");
|
||||
@@ -1245,20 +1268,20 @@ window.addEventListener("load", function() {
|
||||
|
||||
for (x=1; x<tapeDir.length; x++) {
|
||||
row = document.createElement("tr");
|
||||
// File number
|
||||
cell = document.createElement("td");
|
||||
cell.className = "rj";
|
||||
cell.appendChild(document.createTextNode(x.toFixed(0)));
|
||||
row.appendChild(cell);
|
||||
// Load selection checkbox
|
||||
cell = document.createElement("td");
|
||||
cell.className = "center";
|
||||
e = document.createElement("input");
|
||||
e.type = "checkbox";
|
||||
e.id = "File_" + x;
|
||||
e.value = "File_" + x;
|
||||
e.addEventListener("click", resetSelAll);
|
||||
cell.appendChild(e);
|
||||
row.appendChild(cell);
|
||||
// File number
|
||||
cell = document.createElement("td");
|
||||
cell.className = "rj";
|
||||
cell.appendChild(document.createTextNode(x.toFixed(0)));
|
||||
row.appendChild(cell);
|
||||
// File ID
|
||||
cell = document.createElement("td");
|
||||
e = document.createElement("label");
|
||||
@@ -1766,7 +1789,7 @@ window.addEventListener("load", function() {
|
||||
pow2[47-28] + // 28: prevent I/O below user disk area
|
||||
pow2[47-27] + // 27: prevent disk RELEASE statement
|
||||
pow2[47-26] + // 26: printer backup disk release
|
||||
pow2[47-25] + // 25: check memory links
|
||||
// 25: check memory links
|
||||
pow2[47-24] + // 24: type disk error messages
|
||||
pow2[47-23] + // 23: disk logging
|
||||
pow2[47-22] + // 22: suppress library error messages
|
||||
@@ -2093,8 +2116,8 @@ TBODY#TapeDirBody {
|
||||
<table id=TapeDirTable border=1 cellspacing=0 cellpadding=1>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nr
|
||||
<th>Load
|
||||
<th>Nr
|
||||
<th>File ID
|
||||
<th>as MCP
|
||||
<th>as INT
|
||||
|
||||
@@ -19,7 +19,7 @@ DIV#consoleDiv {
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 1125px;
|
||||
height: 140px;
|
||||
height: 136px;
|
||||
border-radius: 8px;
|
||||
background-color: #666}
|
||||
|
||||
@@ -68,7 +68,7 @@ BUTTON.whiteButton {
|
||||
color: black;
|
||||
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
|
||||
font-size: 8pt;
|
||||
font-weight: bold;
|
||||
font-weight: normal;
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: 1px solid #DDD;
|
||||
@@ -80,7 +80,7 @@ BUTTON.greenButton {
|
||||
color: black;
|
||||
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
|
||||
font-size: 8pt;
|
||||
font-weight: bold;
|
||||
font-weight: normal;
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: 1px solid #DDD;
|
||||
@@ -92,7 +92,7 @@ BUTTON.redButton {
|
||||
color: white;
|
||||
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
|
||||
font-size: 8pt;
|
||||
font-weight: bold;
|
||||
font-weight: normal;
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: 1px solid #DDD;
|
||||
@@ -104,7 +104,7 @@ BUTTON.blackButton {
|
||||
color: #999;
|
||||
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
|
||||
font-size: 8pt;
|
||||
font-weight: bold;
|
||||
font-weight: normal;
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: 1px solid #DDD;
|
||||
@@ -116,7 +116,7 @@ BUTTON.yellowButton {
|
||||
color: black;
|
||||
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
|
||||
font-size: 8pt;
|
||||
font-weight: bold;
|
||||
font-weight: normal;
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: 1px solid #DDD;
|
||||
@@ -203,6 +203,7 @@ BUTTON#PowerOffBtn {
|
||||
|
||||
TABLE#CentralControl {
|
||||
position: absolute;
|
||||
left: 32px;
|
||||
bottom: 0;
|
||||
color: #666;
|
||||
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
<script src="./B5500CardReader.js"></script>
|
||||
<script src="./B5500CardPunch.js"></script>
|
||||
<script src="./B5500DummyPrinter.js"></script>
|
||||
<script src="./B5500DatacomUnit.js"></script>
|
||||
<script src="./B5500MagTapeDrive.js"></script>
|
||||
|
||||
<script src="../emulator/B5500SystemConfiguration.js"></script>
|
||||
<script src="../emulator/B5500CentralControl.js"></script>
|
||||
@@ -34,7 +36,7 @@ window.addEventListener("load", function() {
|
||||
var bControl;
|
||||
var bNormal;
|
||||
var boundBlinkenlicht;
|
||||
var cc = new B5500CentralControl();
|
||||
var cc = new B5500CentralControl(this);
|
||||
var intLightsMap = new Array(48);
|
||||
var iouLightsMap = new Array(4);
|
||||
var lastInterruptMask = 0;
|
||||
@@ -150,6 +152,42 @@ window.addEventListener("load", function() {
|
||||
}
|
||||
}
|
||||
|
||||
function displayCallbacks() {
|
||||
/* Builds a table of outstanding callbacks */
|
||||
var cb;
|
||||
var cbs = clearCallback(0);
|
||||
var cookie;
|
||||
var e;
|
||||
var body = document.createElement("tbody");
|
||||
var oldBody = $$("CallbackBody");
|
||||
var row;
|
||||
|
||||
for (cookie in cbs) {
|
||||
cb = cbs[cookie];
|
||||
row = document.createElement("tr");
|
||||
|
||||
e = document.createElement("td");
|
||||
e.appendChild(document.createTextNode(cookie.toString()));
|
||||
row.appendChild(e);
|
||||
|
||||
e = document.createElement("td");
|
||||
e.appendChild(document.createTextNode(cb.delay.toFixed(2)));
|
||||
row.appendChild(e);
|
||||
|
||||
e = document.createElement("td");
|
||||
e.appendChild(document.createTextNode(cb.context.mnemonic || "??"));
|
||||
row.appendChild(e);
|
||||
|
||||
e = document.createElement("td");
|
||||
e.appendChild(document.createTextNode((cb.args ? cb.args.length : 0).toString()));
|
||||
row.appendChild(e);
|
||||
body.appendChild(row);
|
||||
}
|
||||
|
||||
body.id = oldBody.id;
|
||||
oldBody.parentNode.replaceChild(body, oldBody);
|
||||
}
|
||||
|
||||
function displayCentralControl() {
|
||||
/* Displays the I/O and interrupt status in Central Control */
|
||||
var cells;
|
||||
@@ -171,6 +209,14 @@ window.addEventListener("load", function() {
|
||||
$$("AD3F").className = (cc.AD3F ? "busy" : "");
|
||||
$$("AD4F").className = (cc.AD4F ? "busy" : "");
|
||||
|
||||
/********** for P2 debugging **********
|
||||
$$("P2BF").className = (cc.P2BF ? "busy" : "");
|
||||
$$("HP2F").className = (cc.HP2F ? "busy" : "");
|
||||
|
||||
$$("PABZ").className = (cc.PA && cc.PA.busy ? "busy" : "");
|
||||
$$("PBBZ").className = (cc.PB && cc.PB.busy ? "busy" : "");
|
||||
**********/
|
||||
|
||||
x = 0;
|
||||
while (iouChange) {
|
||||
if (iouChange & 0x01) {
|
||||
@@ -203,6 +249,7 @@ window.addEventListener("load", function() {
|
||||
}
|
||||
|
||||
function dasBlinkenlicht() {
|
||||
var cycles;
|
||||
var et;
|
||||
var pa = cc.PA;
|
||||
var pb = cc.PB;
|
||||
@@ -211,16 +258,17 @@ window.addEventListener("load", function() {
|
||||
var stateRate;
|
||||
|
||||
timer = setTimeout(boundBlinkenlicht, timerInterval);
|
||||
cycles = p1.normalCycles+p1.controlCycles;
|
||||
|
||||
if (pa) {
|
||||
if (!pa.busy) {
|
||||
if (pa.normalCycles+pa.controlCycles <= 0) {
|
||||
if (lastPAControlRate != -1) {
|
||||
lastPAControlRate = -1;
|
||||
aControl.className = "yellowButton";
|
||||
aNormal.className = "yellowButton";
|
||||
}
|
||||
} else {
|
||||
stateRate = Math.round(pa.normalCycles/(pa.normalCycles+pa.controlCycles)*6);
|
||||
stateRate = Math.round(pa.normalCycles/cycles*6 + 0.49);
|
||||
if (stateRate != lastPANormalRate) {
|
||||
lastPANormalRate = stateRate;
|
||||
switch (stateRate) {
|
||||
@@ -281,14 +329,14 @@ window.addEventListener("load", function() {
|
||||
}
|
||||
|
||||
if (pb) {
|
||||
if (!pb.busy) {
|
||||
if (pb.normalCycles+pb.controlCycles <= 0) {
|
||||
if (lastPBControlRate != -1) {
|
||||
bControl.className = "yellowButton";
|
||||
bNormal.className = "yellowButton";
|
||||
lastPBControlRate = -1;
|
||||
}
|
||||
} else {
|
||||
stateRate = Math.round(pb.normalCycles/(pb.normalCycles+pb.controlCycles)*6);
|
||||
stateRate = Math.round(pb.normalCycles/cycles*6 + 0.49);
|
||||
if (stateRate != lastPBNormalRate) {
|
||||
lastPBNormalRate = stateRate;
|
||||
switch (stateRate) {
|
||||
@@ -358,6 +406,7 @@ window.addEventListener("load", function() {
|
||||
if (showAnnunciators) {
|
||||
displayCentralControl();
|
||||
}
|
||||
// displayCallbacks();
|
||||
}
|
||||
|
||||
function buildLightMaps() {
|
||||
@@ -482,7 +531,11 @@ window.addEventListener("load", function() {
|
||||
<td id=CCI14F>SPEC <!-- Special interrupt #1 (not used) -->
|
||||
<td id=CCI15F>DK1F <!-- Disk file #1 read check finished -->
|
||||
<td id=CCI16F>DK2F <!-- Disk file #2 read check finished -->
|
||||
<td colspan=13>
|
||||
<td id=P2BF>P2BF <!-- Processor 2 busy FF -->
|
||||
<td id=HP2F>HP2F <!-- Halt Processor 2 FF -->
|
||||
<td id=PABZ>PABZ <!-- Processor A busy *** debug *** -->
|
||||
<td id=PBBZ>PBBZ <!-- Processor B busy *** debug *** -->
|
||||
<td colspan=9>
|
||||
<td id=procSlack>
|
||||
<td class=statLabel title="Percentage of time Processor A is throttling its performance">P1 Slack
|
||||
<tr id=CCPeripheralRow>
|
||||
@@ -522,5 +575,17 @@ window.addEventListener("load", function() {
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<table cellspacing=0 cellpadding=1 border=1 style="position:absolute; top:2in; visibility:hidden">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID
|
||||
<th>Delay
|
||||
<th>Context
|
||||
<th>#Args
|
||||
</thead>
|
||||
<tbody id=CallbackBody>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
61
webUI/B5500DatacomUnit.css
Normal file
61
webUI/B5500DatacomUnit.css
Normal file
@@ -0,0 +1,61 @@
|
||||
/***********************************************************************
|
||||
* retro-b5500/emulator B5500DatacomUnit.css
|
||||
************************************************************************
|
||||
* Copyright (c) 2013, Nigel Williams and Paul Kimpel.
|
||||
* Licensed under the MIT License, see http://www.opensource.org/licenses/mit-license.php
|
||||
************************************************************************
|
||||
* B5500 emulator Datacom web interface style sheet.
|
||||
************************************************************************
|
||||
* 2013-10-19 P.Kimpel
|
||||
* Original version, from B5500SPOUnit.css.
|
||||
***********************************************************************/
|
||||
|
||||
BODY {
|
||||
position: relative;
|
||||
margin: 4px}
|
||||
|
||||
PRE {
|
||||
font-family: Lucida Sans Typewriter, Courier New, Courier, monospace;
|
||||
font-size: 8pt;
|
||||
margin: 0}
|
||||
|
||||
IFRAME#TermOut {
|
||||
height: 480px;
|
||||
width: 560px;
|
||||
border: 1px solid black}
|
||||
|
||||
BODY.TermOut {
|
||||
background-color: white;
|
||||
font-family: Lucida Sans Typewriter, Courier New, Courier, monospace;
|
||||
font-size: 8pt}
|
||||
|
||||
DIV#TermControlsDiv {
|
||||
margin-bottom: 2px;
|
||||
text-align: left}
|
||||
|
||||
BUTTON.greenButton {
|
||||
background-color: #060;
|
||||
color: white;
|
||||
height: 24px;
|
||||
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
|
||||
font-size: 8pt;
|
||||
font-weight: bold;
|
||||
border: 1px solid black;
|
||||
border-radius: 4px}
|
||||
|
||||
BUTTON.greenLit {
|
||||
background-color: #0F0}
|
||||
|
||||
BUTTON.blackBorder {
|
||||
border: 1px solid black}
|
||||
|
||||
SPAN.annunciator {
|
||||
color: black;
|
||||
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
|
||||
font-size: 8pt;
|
||||
font-weight: bold;
|
||||
visibility: hidden}
|
||||
|
||||
SPAN.textLit {
|
||||
color: red;
|
||||
visibility: visible}
|
||||
43
webUI/B5500DatacomUnit.html
Normal file
43
webUI/B5500DatacomUnit.html
Normal file
@@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<title>B5500 Emulator Datacom Unit</title>
|
||||
<meta name="Author" content="Nigel Williams & Paul Kimpel">
|
||||
<!-- 2013-10-19 Original version, cloned from B5500SPOUnit.html -->
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
|
||||
<meta http-equiv="Content-Script-Type" content="text/javascript">
|
||||
<meta http-equiv="Content-Style-Type" content="text/css">
|
||||
<link id=defaultStyleSheet rel=stylesheet type="text/css" href="B5500DatacomUnit.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id=TermControlsDiv>
|
||||
<button id=TermConnectBtn class="greenButton">Connect</button>
|
||||
|
||||
<span id=NotReadyState class="annunciator">NR</span>
|
||||
<span id=IdleState class="annunciator">IDLE</span>
|
||||
<span id=ReadReadyState class="annunciator">RR</span>
|
||||
<span id=WriteReadyState class="annunciator">WR</span>
|
||||
<span id=InputBusyState class="annunciator">IBZ</span>
|
||||
<span id=OutputBusyState class="annunciator">OBZ</span>
|
||||
<span id=Abnormal class="annunciator">AB</span>
|
||||
<span id=Interrupt class="annunciator">INT</span>
|
||||
<span id=FullBuffer class="annunciator">FB</span>
|
||||
|
||||
Offset:
|
||||
<span id=BufferOffset class="annunciator textLit">0</span>
|
||||
Length:
|
||||
<span id=BufferLength class="annunciator textLit">0</span>
|
||||
Col:
|
||||
<span id=PrintColumn class="annunciator textLit">0</span>
|
||||
<!--
|
||||
<br>Key:
|
||||
<span id=CharCode class="annunciator textLit"></span>
|
||||
<span id=KeyCode class="annunciator textLit"></span>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<iframe id=TermOut scrolling=auto></iframe>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
854
webUI/B5500DatacomUnit.js
Normal file
854
webUI/B5500DatacomUnit.js
Normal file
@@ -0,0 +1,854 @@
|
||||
/***********************************************************************
|
||||
* retro-b5500/emulator B5500DatacomUnit.js
|
||||
************************************************************************
|
||||
* Copyright (c) 2012, Nigel Williams and Paul Kimpel.
|
||||
* Licensed under the MIT License, see
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
************************************************************************
|
||||
* B5500 Datacom Peripheral Unit module.
|
||||
*
|
||||
* Defines a Datacom peripheral unit type that implements:
|
||||
* - The B249 Data Transmission Control Unit (DTCU), with
|
||||
* - A single B487 Data Transmission Terminal Unit (DTTU), having
|
||||
* - A single type 980 (teletype) adapter with a 112-character buffer.
|
||||
*
|
||||
* The user interface emulates a simple teletype device, similar to the SPO.
|
||||
*
|
||||
* Note that the results from the DCA are unusual, in that the terminal unit
|
||||
* (TU) and buffer numbers are returned in [8:10] of the error mask.
|
||||
*
|
||||
************************************************************************
|
||||
* 2013-10-19 P.Kimpel
|
||||
* Original version, cloned from B5500SPOUnit.js.
|
||||
***********************************************************************/
|
||||
"use strict";
|
||||
|
||||
/**************************************/
|
||||
function B5500DatacomUnit(mnemonic, unitIndex, designate, statusChange, signal) {
|
||||
/* Constructor for the DatacomUnit object */
|
||||
|
||||
this.maxScrollLines = 1500; // Maximum amount of printer scrollback
|
||||
this.charPeriod = 100; // Printer speed, milliseconds per character
|
||||
this.bufferSize = 112; // 4 B487 buffer segments
|
||||
|
||||
this.mnemonic = mnemonic; // Unit mnemonic
|
||||
this.unitIndex = unitIndex; // Ready-mask bit number
|
||||
this.designate = designate; // IOD unit designate number
|
||||
this.statusChange = statusChange; // external function to call for ready-status change
|
||||
this.signal = signal; // external function to call for special signals (e.g,. Datacom inquiry request)
|
||||
|
||||
this.buffer = new ArrayBuffer(448); // adapter buffer storage
|
||||
this.initiateStamp = 0; // timestamp of last initiation (set by IOUnit)
|
||||
this.inTimer = null; // input setCallback() token
|
||||
this.outTimer = null; // output setCallback() token
|
||||
|
||||
this.clear();
|
||||
|
||||
this.window = window.open("", mnemonic);
|
||||
if (this.window) {
|
||||
this.shutDown(); // destroy any previously-existing window
|
||||
this.window = null;
|
||||
}
|
||||
this.doc = null;
|
||||
this.paper = null;
|
||||
this.endOfPaper = null;
|
||||
this.window = window.open("../webUI/B5500DatacomUnit.html", mnemonic,
|
||||
"scrollbars,resizable,width=580,height=540");
|
||||
this.window.moveTo((screen.availWidth-this.window.outerWidth)/2, (screen.availHeight-this.window.outerHeight)/2);
|
||||
this.window.addEventListener("load", B5500CentralControl.bindMethod(B5500DatacomUnit.prototype.datacomOnload, this), false);
|
||||
}
|
||||
|
||||
// this.bufState enumerations
|
||||
B5500DatacomUnit.prototype.bufNotReady = 0;
|
||||
B5500DatacomUnit.prototype.bufIdle = 1;
|
||||
B5500DatacomUnit.prototype.bufInputBusy = 2;
|
||||
B5500DatacomUnit.prototype.bufReadReady = 3;
|
||||
B5500DatacomUnit.prototype.bufOutputBusy = 4;
|
||||
B5500DatacomUnit.prototype.bufWriteReady = 5;
|
||||
|
||||
B5500DatacomUnit.prototype.keyFilter = [ // Filter keyCode values to valid BCL ones
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 00-0F
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 10-1F
|
||||
0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x00,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F, // 20-2F
|
||||
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x00,0x3D,0x00,0x3F, // 30-3F
|
||||
0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, // 40-4F
|
||||
0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x00,0x5D,0x00,0x00, // 50-5F
|
||||
0x00,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, // 60-6F
|
||||
0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x7B,0x7C,0x7D,0x7E,0x00]; // 70-7F
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.$$ = function $$(e) {
|
||||
return this.doc.getElementById(e);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.clear = function clear() {
|
||||
/* Initializes (and if necessary, creates) the datacom unit state */
|
||||
|
||||
this.ready = false; // ready status
|
||||
this.busy = false; // busy status
|
||||
|
||||
this.abnormal = false; // buffer in abnormal state
|
||||
this.bufIndex = 0; // current offset into buffer
|
||||
this.bufLength = 0; // current buffer length
|
||||
this.connected = false; // buffer/adapter is currently connected
|
||||
this.errorMask = 0; // error mask for finish()
|
||||
this.finish = null; // external function to call for I/O completion
|
||||
this.fullBuffer = false; // buffer is full (unterminated)
|
||||
this.interrupt = false; // buffer in interrupt state
|
||||
this.nextCharTime = 0; // next output character time
|
||||
this.printCol = 0; // current printer column
|
||||
|
||||
this.bufState = this.bufNotReady; // Current state of datacom buffer
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.hasClass = function hasClass(e, name) {
|
||||
/* returns true if element "e" has class "name" in its class list */
|
||||
var classes = e.className;
|
||||
|
||||
if (!e) {
|
||||
return false;
|
||||
} else if (classes == name) {
|
||||
return true;
|
||||
} else {
|
||||
return (classes.search("\\b" + name + "\\b") >= 0);
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.addClass = function addClass(e, name) {
|
||||
/* Adds a class "name" to the element "e"s class list */
|
||||
|
||||
if (!this.hasClass(e, name)) {
|
||||
e.className += (" " + name);
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.removeClass = function removeClass(e, name) {
|
||||
/* Removes the class "name" from the element "e"s class list */
|
||||
|
||||
e.className = e.className.replace(new RegExp("\\b" + name + "\\b\\s*", "g"), "");
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.showBufferIndex = function showBufferIndex() {
|
||||
/* Formats the buffer index and length, and the column counter, for display */
|
||||
|
||||
this.$$("BufferOffset").innerHTML = this.bufIndex.toString();
|
||||
this.$$("BufferLength").innerHTML = this.bufLength.toString();
|
||||
this.$$("PrintColumn").innerHTML = (this.printCol+1).toString();
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.setState = function setState(newState) {
|
||||
/* Sets a new state in this.bufState and updates the annunciators appropriately */
|
||||
|
||||
this.showBufferIndex();
|
||||
|
||||
if (this.abnormal) {
|
||||
this.addClass(this.$$("Abnormal"), "textLit")
|
||||
} else {
|
||||
this.removeClass(this.$$("Abnormal"), "textLit");
|
||||
}
|
||||
|
||||
if (this.interrupt) {
|
||||
this.addClass(this.$$("Interrupt"), "textLit")
|
||||
} else {
|
||||
this.removeClass(this.$$("Interrupt"), "textLit");
|
||||
}
|
||||
|
||||
if (this.fullBuffer) {
|
||||
this.addClass(this.$$("FullBuffer"), "textLit")
|
||||
} else {
|
||||
this.removeClass(this.$$("FullBuffer"), "textLit");
|
||||
}
|
||||
|
||||
if (this.bufState != newState) {
|
||||
switch (this.bufState) {
|
||||
case this.bufNotReady:
|
||||
this.removeClass(this.$$("NotReadyState"), "textLit");
|
||||
break;
|
||||
case this.bufIdle:
|
||||
this.removeClass(this.$$("IdleState"), "textLit");
|
||||
break;
|
||||
case this.bufInputBusy:
|
||||
this.removeClass(this.$$("InputBusyState"), "textLit");
|
||||
break;
|
||||
case this.bufReadReady:
|
||||
this.removeClass(this.$$("ReadReadyState"), "textLit");
|
||||
break;
|
||||
case this.bufOutputBusy:
|
||||
this.removeClass(this.$$("OutputBusyState"), "textLit");
|
||||
break;
|
||||
case this.bufWriteReady:
|
||||
this.removeClass(this.$$("WriteReadyState"), "textLit");
|
||||
break;
|
||||
}
|
||||
|
||||
switch (newState) {
|
||||
case this.bufNotReady:
|
||||
this.addClass(this.$$("NotReadyState"), "textLit");
|
||||
break;
|
||||
case this.bufIdle:
|
||||
this.addClass(this.$$("IdleState"), "textLit");
|
||||
break;
|
||||
case this.bufInputBusy:
|
||||
this.addClass(this.$$("InputBusyState"), "textLit");
|
||||
break;
|
||||
case this.bufReadReady:
|
||||
this.addClass(this.$$("ReadReadyState"), "textLit");
|
||||
break;
|
||||
case this.bufOutputBusy:
|
||||
this.addClass(this.$$("OutputBusyState"), "textLit");
|
||||
break;
|
||||
case this.bufWriteReady:
|
||||
this.addClass(this.$$("WriteReadyState"), "textLit");
|
||||
break;
|
||||
}
|
||||
|
||||
this.bufState = newState;
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.termDisconnect = function termDisconnect() {
|
||||
/* Sets the status of the datacom unit to disconnected */
|
||||
|
||||
if (this.connected) {
|
||||
this.bufLength = 0;
|
||||
this.bufIndex = 0;
|
||||
this.removeClass(this.$$("TermConnectBtn"), "greenLit");
|
||||
this.interrupt = true;
|
||||
this.abnormal = true;
|
||||
this.setState(this.bufIdle);
|
||||
this.signal();
|
||||
this.connected = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.termConnect = function termConnect() {
|
||||
/* Sets the status of the datacom unit to connected */
|
||||
|
||||
if (!this.connected) {
|
||||
this.addClass(this.$$("TermConnectBtn"), "greenLit");
|
||||
this.interrupt = true;
|
||||
this.abnormal = true;
|
||||
this.setState(this.bufWriteReady);
|
||||
this.signal();
|
||||
this.connected = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.appendEmptyLine = function appendEmptyLine() {
|
||||
/* Removes excess lines already printed, then appends a new <pre> element
|
||||
to the <iframe>, creating an empty text node inside the new element */
|
||||
var count = this.paper.childNodes.length;
|
||||
var line = this.doc.createTextNode("");
|
||||
|
||||
while (count-- > this.maxScrollLines) {
|
||||
this.paper.removeChild(this.paper.firstChild);
|
||||
}
|
||||
this.paper.lastChild.nodeValue += String.fromCharCode(0x0A); // newline
|
||||
this.endOfPaper.scrollIntoView();
|
||||
this.paper.appendChild(line);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.backspaceChar = function backspaceChar() {
|
||||
/* Handles backspace for datacom buffer input */
|
||||
var line = this.paper.lastChild;
|
||||
|
||||
if (this.bufLength > 0) {
|
||||
this.bufIndex--;
|
||||
this.showBufferIndex();
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.printChar = function printChar(c) {
|
||||
/* Echoes the character code "c" to the terminal display */
|
||||
var col = this.printCol;
|
||||
var line = this.paper.lastChild.nodeValue;
|
||||
var len = line.length;
|
||||
|
||||
if (col < 72) {
|
||||
while (len < col) {
|
||||
line += " ";
|
||||
len++;
|
||||
}
|
||||
if (len > col) {
|
||||
line = line.substring(0, col) + String.fromCharCode(c) + line.substring(col+1);
|
||||
} else {
|
||||
line += String.fromCharCode(c);
|
||||
}
|
||||
this.printCol++;
|
||||
} else {
|
||||
line = line.substring(0, 71) + String.fromCharCode(c);
|
||||
}
|
||||
this.paper.lastChild.nodeValue = line;
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.outputChar = function outputChar() {
|
||||
/* Outputs one character from the buffer to the terminal display. If more characters remain
|
||||
to be printed, schedules itself 100 ms later to print the next one, otherwise
|
||||
calls finished(). If the column counter exceeds 72, the last character over-types.
|
||||
Note that Group Mark (left-arrow) detection is done by IOUnit in preparing the buffer */
|
||||
var c;
|
||||
var delay;
|
||||
var nextTime;
|
||||
var stamp;
|
||||
|
||||
if (this.bufIndex < this.bufLength) {
|
||||
stamp = new Date().getTime();
|
||||
nextTime = (this.nextCharTime < stamp ? stamp : this.nextCharTime) + this.charPeriod;
|
||||
delay = nextTime - stamp;
|
||||
this.nextCharTime = nextTime;
|
||||
|
||||
c = this.buffer[this.bufIndex++];
|
||||
switch (c) {
|
||||
case 0x7B: // { less-or-equal, output CR
|
||||
this.printCol = 0;
|
||||
this.outTimer = setCallback(this.outputChar, this, delay);
|
||||
break;
|
||||
case 0x21: // ! not-equal, output LF
|
||||
this.appendEmptyLine();
|
||||
this.outTimer = setCallback(this.outputChar, this, delay);
|
||||
break;
|
||||
case 0x3C: // < less-than, output RO (DEL)
|
||||
case 0x3E: // > greater-than, output X-ON (DC1)
|
||||
this.outTimer = setCallback(this.outputChar, this, delay);
|
||||
break; // do nothing, just delay
|
||||
case 0x7D: // } greater-or-equal, disconnect
|
||||
this.termDisconnect();
|
||||
break;
|
||||
default:
|
||||
this.printChar(c);
|
||||
this.outTimer = setCallback(this.outputChar, this, delay);
|
||||
break;
|
||||
}
|
||||
this.showBufferIndex();
|
||||
} else {
|
||||
this.interrupt = true;
|
||||
this.setState(this.fullBuffer ? this.bufWriteReady : this.bufIdle);
|
||||
this.signal();
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.terminateInput = function terminateInput() {
|
||||
/* Handles the End of Message event */
|
||||
|
||||
this.interrupt = true;
|
||||
this.setState(this.bufReadReady);
|
||||
this.signal();
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.keyPress = function keyPress(ev) {
|
||||
/* Handles keyboard character events. Depending on the state of the unit,
|
||||
either buffers the character for transmission to the I/O Unit, echos
|
||||
it to the printer, or ignores it altogether */
|
||||
var c = ev.charCode;
|
||||
var delay;
|
||||
var index = this.bufLength;
|
||||
var nextTime;
|
||||
var stamp;
|
||||
|
||||
//this.$$("CharCode").innerHTML = c.toString() + ":0x" + c.toString(16);
|
||||
|
||||
if (this.connected) {
|
||||
stamp = new Date().getTime();
|
||||
if (this.bufState == this.bufIdle) {
|
||||
this.bufIndex = this.bufLength = 0;
|
||||
this.nextCharTime = stamp;
|
||||
}
|
||||
|
||||
nextTime = this.nextCharTime + this.charPeriod;
|
||||
delay = nextTime - stamp;
|
||||
|
||||
if (c == 0) {
|
||||
switch(ev.keyCode) {
|
||||
case 0x08:
|
||||
c = 0x3C; // BS: force Backspace key
|
||||
break;
|
||||
case 0x0D:
|
||||
c = 0x7E; // Enter: force ~ (GM) for end-of-message
|
||||
break;
|
||||
}
|
||||
} else if (ev.ctrlKey) {
|
||||
switch(c) {
|
||||
case 0x42:
|
||||
case 0x62:
|
||||
c = 0x02; // Ctrl-B: force STX, break
|
||||
break;
|
||||
case 0x45:
|
||||
case 0x65:
|
||||
c = 0x05; // Ctrl-E:force ENQ, WRU
|
||||
break;
|
||||
case 0x4C:
|
||||
case 0x6C:
|
||||
c = 0x0C; // Ctrl-L: force FF, clear input buffer
|
||||
break;
|
||||
case 0x51:
|
||||
case 0x71:
|
||||
c = 0x7E; // Ctrl-Q: DC1, X-ON to ~ (GM) for end-of-message
|
||||
break;
|
||||
default:
|
||||
c = 0; // not something we want
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.bufState == this.bufReadReady && this.fullBuffer) {
|
||||
this.interrupt = true;
|
||||
this.setState(this.bufInputBusy);
|
||||
this.signal(); // buffer overflow
|
||||
} else if (this.bufState == this.bufInputBusy || this.bufState == this.bufIdle) {
|
||||
switch (c) {
|
||||
case 0x7E: // ~ left-arrow (Group Mark), end of message
|
||||
this.inTimer = setCallback(this.printChar, this, delay, c);
|
||||
this.nextCharTime = nextTime;
|
||||
this.terminateInput();
|
||||
ev.stopPropagation();
|
||||
break;
|
||||
case 0x3C: // <, backspace
|
||||
if (this.bufIndex > 0) {
|
||||
this.bufIndex--;
|
||||
}
|
||||
this.inTimer = setCallback(this.printChar, this, delay, c);
|
||||
this.nextCharTime = nextTime;
|
||||
ev.stopPropagation();
|
||||
break;
|
||||
case 0x21: // ! EOT, disconnect
|
||||
//this.buffer[this.bufIndex++] = 0x7D; // } greater-or-equal code
|
||||
this.interrupt = true;
|
||||
this.abnomal = true;
|
||||
this.setState(this.bufReadReady);
|
||||
this.signal();
|
||||
this.inTimer = setCallback(this.printChar, this, delay, c);
|
||||
this.nextCharTime = nextTime;
|
||||
ev.stopPropagation();
|
||||
break;
|
||||
case 0x02: // Ctrl-B, STX, break on output
|
||||
if (this.bufState == this.bufOutputBusy) {
|
||||
this.interrupt = true;
|
||||
this.abnormal = true;
|
||||
this.setState(this.bufReadReady);
|
||||
this.signal();
|
||||
} else if (this.bufState == this.bufInputBusy) {
|
||||
this.bufIndex = this.bufLength = 0;
|
||||
this.setState(this.bufIdle);
|
||||
}
|
||||
break;
|
||||
case 0x05: // Ctrl-E, ENQ, who-are-you (WRU)
|
||||
if (this.bufState == this.bufIdle || this.bufState == this.bufInputBusy) {
|
||||
this.interrupt = true;
|
||||
this.abnormal = true;
|
||||
this.setState(this.bufWriteReady);
|
||||
this.signal();
|
||||
}
|
||||
break;
|
||||
case 0x0C: // Ctrl-L, FF, clear input buffer
|
||||
if (this.bufState == this.bufInputBusy) {
|
||||
this.bufIndex = this.bufLength = 0;
|
||||
}
|
||||
break;
|
||||
case 0x3F: // ? question-mark, set abnormal for control message
|
||||
this.abnormal = true;
|
||||
this.setState(this.bufState); // just to turn on the annunciator
|
||||
// no break
|
||||
default:
|
||||
c = this.keyFilter[c];
|
||||
if (c) { // if it's a character we will accept
|
||||
this.buffer[this.bufIndex++] = c;
|
||||
this.inTimer = setCallback(this.printChar, this, delay, c);
|
||||
this.nextCharTime = nextTime;
|
||||
ev.stopPropagation();
|
||||
if (this.bufIndex < this.bufferSize) {
|
||||
this.setState(this.bufInputBusy);
|
||||
} else {
|
||||
this.interrupt = true;
|
||||
this.fullBuffer = true;
|
||||
this.setState(this.bufReadReady);
|
||||
this.signal(); // full buffer, no GM detected
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ev.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.keyDown = function keyDown(ev) {
|
||||
/* Handles key-down events to capture Ctrl-E (ENQ), Ctrl-L (FF), Ctrl-Q (DC1),
|
||||
and Enter keystrokes */
|
||||
var c = ev.keyCode;
|
||||
var delay;
|
||||
var nextTime;
|
||||
var result = true;
|
||||
var stamp;
|
||||
|
||||
//this.$$("KeyCode").innerHTML = "KC " + c.toString() + ":0x" + c.toString(16) +
|
||||
// (ev.shiftKey ? "S" : " ") + (ev.ctrlKey ? "C" : " ") + (ev.altKey ? "A" : " ");
|
||||
|
||||
if (this.connected) {
|
||||
stamp = new Date().getTime();
|
||||
nextTime = (this.nextCharTime < stamp ? stamp : this.nextCharTime) + this.charPeriod;
|
||||
delay = nextTime - stamp;
|
||||
|
||||
switch (c) {
|
||||
case 0x08: // Backspace
|
||||
if (this.bufState == this.bufInputBusy) {
|
||||
if (this.bufIndex > 0) {
|
||||
this.bufIndex--;
|
||||
}
|
||||
this.inTimer = setCallback(this.printChar, this, delay, 0x3C);
|
||||
this.nextCharTime = nextTime;
|
||||
}
|
||||
break;
|
||||
case 0x0D: // Enter
|
||||
//case 0x11: // Ctrl-Q, DC1 (X-ON)
|
||||
if (this.bufState == this.bufInputBusy || this.bufState == this.bufIdle) {
|
||||
this.inTimer = setCallback(this.printChar, this, delay, 0x7E);
|
||||
this.nextCharTime = nextTime;
|
||||
this.terminateInput();
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 0x02: // Ctrl-B, STX, break on output
|
||||
if (this.bufState == this.bufOutputBusy) {
|
||||
this.interrupt = true;
|
||||
this.abnormal = true;
|
||||
this.setState(this.bufReadReady);
|
||||
this.signal();
|
||||
} else if (this.bufState == this.bufInputBusy) {
|
||||
this.bufIndex = this.bufLength = 0;
|
||||
this.setState(this.bufIdle);
|
||||
}
|
||||
break;
|
||||
case 0x05: // Ctrl-E, ENQ, who-are-you (WRU)
|
||||
if (this.bufState == this.bufIdle || this.bufState == this.bufInputBusy) {
|
||||
this.interrupt = true;
|
||||
this.abnormal = true;
|
||||
this.setState(this.bufWriteReady);
|
||||
this.signal();
|
||||
}
|
||||
break;
|
||||
case 0x0C: // Ctrl-L, FF, clear input buffer
|
||||
if (this.bufState == this.bufInputBusy) {
|
||||
this.bufIndex = this.bufLength = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
|
||||
this.showBufferIndex();
|
||||
if (result) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.termConnectBtnClick = function termConnectBtnClick(ev) {
|
||||
|
||||
if (this.connected) {
|
||||
this.termDisconnect();
|
||||
} else {
|
||||
this.termConnect();
|
||||
}
|
||||
ev.target.blur(); // move focus off the Connect btn
|
||||
this.paper.focus();
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.beforeUnload = function beforeUnload(ev) {
|
||||
var msg = "Closing this window will make the device unusable.\n" +
|
||||
"Suggest you stay on the page and minimize this window instead";
|
||||
|
||||
ev.preventDefault();
|
||||
ev.returnValue = msg;
|
||||
return msg;
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.datacomOnload = function datacomOnload() {
|
||||
/* Initializes the datacom unit and terminal window user interface */
|
||||
var x;
|
||||
|
||||
this.doc = this.window.document;
|
||||
this.doc.title = "retro-B5500 " + this.mnemonic + " TU1/BUF0";
|
||||
this.paper = this.doc.createElement("pre");
|
||||
this.paper.appendChild(this.doc.createTextNode(""));
|
||||
this.$$("TermOut").contentDocument.body.appendChild(this.paper);
|
||||
this.endOfPaper = this.doc.createElement("div");
|
||||
this.endOfPaper.appendChild(this.doc.createTextNode("\xA0"));
|
||||
this.$$("TermOut").contentDocument.body.appendChild(this.endOfPaper);
|
||||
this.$$("TermOut").contentDocument.head.innerHTML += "<style>" +
|
||||
"BODY {background-color: white} " +
|
||||
"PRE {margin: 0; font-size: 8pt; font-family: Lucida Sans Typewriter, Courier New, Courier, monospace}" +
|
||||
"</style>";
|
||||
|
||||
this.window.focus();
|
||||
this.nextCharTime = new Date().getTime();
|
||||
|
||||
this.window.addEventListener("beforeunload", this.beforeUnload, false);
|
||||
|
||||
this.window.addEventListener("keypress", B5500CentralControl.bindMethod(B5500DatacomUnit.prototype.keyPress, this), false);
|
||||
this.$$("TermOut").contentDocument.body.addEventListener("keypress", B5500CentralControl.bindMethod(B5500DatacomUnit.prototype.keyPress, this), false);
|
||||
|
||||
this.$$("TermConnectBtn").addEventListener("click", B5500CentralControl.bindMethod(B5500DatacomUnit.prototype.termConnectBtnClick, this), false);
|
||||
|
||||
for (x=0; x<32; x++) {
|
||||
this.appendEmptyLine();
|
||||
}
|
||||
this.statusChange(1); // make DCA ready
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.read = function read(finish, buffer, length, mode, control) {
|
||||
/* Initiates a read operation on the unit. "control" is the TU/BUF# */
|
||||
var actualLength = 0;
|
||||
var bufNr;
|
||||
var tuNr;
|
||||
var transparent;
|
||||
var x;
|
||||
|
||||
this.errorMask = 0x100 + (mode & 0x01)*0x800; // set the read [24:1] and mode [21:1] bits in the mask
|
||||
bufNr = control % 0x10;
|
||||
transparent = (control % 0x20) >>> 4;
|
||||
tuNr = (control % 0x200) >>> 5;
|
||||
|
||||
switch (true) {
|
||||
case tuNr != 1:
|
||||
case bufNr != 0:
|
||||
this.errorMask |= 0x34; // not this TU/BUF -- set buffer not ready
|
||||
break;
|
||||
case !this.connected:
|
||||
this.errorMask |= 0x34; // not connected -- set buffer not ready
|
||||
break;
|
||||
case this.bufState == this.bufReadReady:
|
||||
// Copy the adaptor buffer to the IOUnit buffer
|
||||
actualLength = (transparent ? this.bufferSize : this.bufIndex);
|
||||
for (x=0; x<actualLength; x++) {
|
||||
buffer[x] = this.buffer[x];
|
||||
}
|
||||
|
||||
// Set the state bits in the result and reset the adaptor to idle
|
||||
if (this.abnormal) {
|
||||
this.errorMask |= 0x200; // set abnormal bit
|
||||
}
|
||||
if (this.fullBuffer || transparent) {
|
||||
this.errorMask |= 0x80; // set no-GM/buffer-exhausted bit
|
||||
}
|
||||
this.bufIndex = this.bufLength = 0;
|
||||
this.interrupt = false;
|
||||
this.abnormal = false;
|
||||
this.fullBuffer = false;
|
||||
this.setState(this.bufIdle);
|
||||
break;
|
||||
case this.bufState == this.bufWriteReady:
|
||||
this.errorMask |= 0x20; // attempt to read a write-ready buffer
|
||||
break;
|
||||
case this.bufState == this.bufInputBusy:
|
||||
case this.bufState == this.bufOutputBusy:
|
||||
this.errorMask |= 0x30; // buffer busy
|
||||
break;
|
||||
default:
|
||||
this.errorMask |= 0x34; // buffer idle or not ready
|
||||
break;
|
||||
} // switch (true)
|
||||
|
||||
this.errorMask += bufNr*0x40000000 + tuNr*0x800000000;
|
||||
|
||||
//console.log(this.mnemonic + " Read: " + control.toString(8) + ":" + this.errorMask.toString(8) + " (" +
|
||||
// actualLength + ") " + (actualLength ? String.fromCharCode.apply(null, buffer.subarray(0, actualLength)) : 0));
|
||||
|
||||
finish(this.errorMask, actualLength);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.space = function space(finish, length, control) {
|
||||
/* Initiates a space operation on the unit */
|
||||
|
||||
finish(0x04, 0); // report unit not ready
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.write = function write(finish, buffer, length, mode, control) {
|
||||
/* Initiates a write operation on the unit. "control" is the TU/BUF# */
|
||||
var actualLength = 0;
|
||||
var bufNr;
|
||||
var tuNr;
|
||||
var transparent;
|
||||
var x;
|
||||
|
||||
this.errorMask = (mode & 0x01)*0x800; // set the mode [21:1] bit in the mask
|
||||
bufNr = control % 0x10;
|
||||
transparent = (control % 0x20) >>> 4;
|
||||
tuNr = (control % 0x200) >>> 5;
|
||||
|
||||
switch (true) {
|
||||
case tuNr != 1:
|
||||
case bufNr != 0:
|
||||
this.errorMask |= 0x34; // not this TU/BUF -- buffer not ready
|
||||
break;
|
||||
case !this.connected:
|
||||
this.errorMask |= 0x34; // not connected -- buffer not ready
|
||||
break;
|
||||
case this.bufState == this.bufIdle:
|
||||
case this.bufState == this.bufWriteReady:
|
||||
// Copy the IOUnit buffer to the adapter buffer
|
||||
if (transparent) {
|
||||
actualLength = this.bufferSize;
|
||||
this.fullBuffer = false; // or should it be true for transparent?
|
||||
} else if (length < this.bufferSize) {
|
||||
actualLength = length;
|
||||
this.fullBuffer = false;
|
||||
} else {
|
||||
actualLength = this.bufferSize;
|
||||
this.fullBuffer = true;
|
||||
}
|
||||
for (x=0; x<actualLength; x++) {
|
||||
this.buffer[x] = buffer[x];
|
||||
}
|
||||
|
||||
// Set the state bits in the result and start printing
|
||||
if (this.abnormal) {
|
||||
this.errorMask |= 0x200; // set abnormal bit
|
||||
}
|
||||
if (this.fullBuffer || transparent) {
|
||||
this.errorMask |= 0x80; // set no-GM/buffer-exhausted bit
|
||||
}
|
||||
this.bufIndex = 0;
|
||||
this.bufLength = actualLength;
|
||||
this.interrupt = false;
|
||||
this.abnormal = false;
|
||||
this.setState(this.bufOutputBusy);
|
||||
this.nextCharTime = this.initiateStamp;
|
||||
this.outputChar(); // start the printing process
|
||||
break;
|
||||
case this.bufState == this.bufReadReady:
|
||||
this.errorMask |= 0x20; // attempt to write a read-ready buffer
|
||||
break;
|
||||
case this.bufState == this.bufInputBusy:
|
||||
case this.bufState == this.bufOutputBusy:
|
||||
this.errorMask |= 0x30; // buffer busy
|
||||
break;
|
||||
default:
|
||||
this.errorMask |= 0x34; // buffer not ready
|
||||
break;
|
||||
} // switch (true)
|
||||
|
||||
this.errorMask += bufNr*0x40000000 + tuNr*0x800000000;
|
||||
|
||||
//console.log(this.mnemonic + " Write: " + control.toString(8) + ":" + this.errorMask.toString(8) + " (" +
|
||||
// actualLength + ") " + (actualLength ? String.fromCharCode.apply(null, buffer.subarray(0, actualLength)) : ""));
|
||||
|
||||
finish(this.errorMask, actualLength);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.erase = function erase(finish, length) {
|
||||
/* Initiates an erase operation on the unit */
|
||||
|
||||
finish(0x04, 0); // report unit not ready
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.rewind = function rewind(finish) {
|
||||
/* Initiates a rewind operation on the unit */
|
||||
|
||||
finish(0x04, 0); // report unit not ready
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.readCheck = function readCheck(finish, length, control) {
|
||||
/* Initiates a read check operation on the unit */
|
||||
|
||||
finish(0x04, 0); // report unit not ready
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.readInterrogate = function readInterrogate(finish, control) {
|
||||
/* Initiates a read interrogate operation on the unit */
|
||||
|
||||
finish(0x04, 0); // report unit not ready
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.writeInterrogate = function writeInterrogate(finish, control) {
|
||||
/* Initiates a write interrogate operation on the unit. "control" is the TU/BUF# */
|
||||
var bufNr;
|
||||
var tuNr;
|
||||
|
||||
this.errorMask = 0; // default result is idle
|
||||
bufNr = control % 0x10;
|
||||
tuNr = (control % 0x200) >>> 5;
|
||||
|
||||
if (tuNr > 1) {
|
||||
this.errorMask |= 0x34; // not a valid TU/BUF -- report not ready
|
||||
} else if (tuNr == 1 && bufNr > 0) {
|
||||
this.errorMask |= 0x34 // not a valid BUF for TU#1 -- report not ready
|
||||
} else if (this.interrupt) {
|
||||
tuNr = 1; // it must be for TU=1, BUF=0
|
||||
bufNr = 0;
|
||||
switch (this.bufState) {
|
||||
case this.bufReadReady:
|
||||
this.errorMask |= 0x100;
|
||||
break;
|
||||
case this.bufWriteReady:
|
||||
this.errorMask |= 0x20;
|
||||
break;
|
||||
case this.bufInputBusy:
|
||||
case this.bufOUtputBusy:
|
||||
this.errorMask |= 0x10;
|
||||
break;
|
||||
case this.bufIdle:
|
||||
// default value, no action
|
||||
break;
|
||||
default:
|
||||
this.errorMask |= 0x14; // report not ready
|
||||
break;
|
||||
} // switch (this.bufState)
|
||||
|
||||
if (this.abnormal) {
|
||||
this.errorMask |= 0x200; // set abnormal bit
|
||||
}
|
||||
this.interrupt = false;
|
||||
this.setState(this.bufState);
|
||||
}
|
||||
|
||||
this.errorMask += bufNr*0x40000000 + tuNr*0x800000000;
|
||||
|
||||
//console.log(this.mnemonic + " W-Int: " + control.toString(8) + ":" + this.errorMask.toString(8));
|
||||
|
||||
finish(this.errorMask, 0);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500DatacomUnit.prototype.shutDown = function shutDown() {
|
||||
/* Shuts down the device */
|
||||
|
||||
if (this.inTimer) {
|
||||
clearTimeout(this.inTimer);
|
||||
}
|
||||
if (this.outTimer) {
|
||||
clearTimeout(this.outTimer);
|
||||
}
|
||||
this.window.removeEventListener("beforeunload", this.beforeUnload, false);
|
||||
this.window.close();
|
||||
};
|
||||
@@ -43,15 +43,15 @@ function B5500DummyPrinter(mnemonic, unitIndex, designate, statusChange, signal)
|
||||
this.doc = null;
|
||||
this.paper = null;
|
||||
this.endOfPaper = null;
|
||||
this.window = window.open("/B5500/webUI/B5500DummyPrinter.html", mnemonic,
|
||||
this.window = window.open("../webUI/B5500DummyPrinter.html", mnemonic,
|
||||
s = "scrollbars,resizable,width=" + w + ",height=" + h +
|
||||
",left=0,top=" + (screen.availHeight - h));
|
||||
this.window.addEventListener("load", function windowOnLoad() {
|
||||
that.printerOnload();
|
||||
}, false);
|
||||
}
|
||||
B5500DummyPrinter.prototype.linesPerMinute = 1040; // B329 line printer
|
||||
B5500DummyPrinter.maxScrollLines = 150000; // Maximum printer scrollback (about a box of paper)
|
||||
B5500DummyPrinter.prototype.linesPerMinute = 1040; // B329 line printer
|
||||
B5500DummyPrinter.prototype.maxScrollLines = 150000; // Maximum printer scrollback (about a box of paper)
|
||||
|
||||
|
||||
/**************************************/
|
||||
|
||||
144
webUI/B5500MagTapeDrive.css
Normal file
144
webUI/B5500MagTapeDrive.css
Normal file
@@ -0,0 +1,144 @@
|
||||
/***********************************************************************
|
||||
* retro-b5500/emulator B5500MagTapeDrive.css
|
||||
************************************************************************
|
||||
* Copyright (c) 2013, Nigel Williams and Paul Kimpel.
|
||||
* Licensed under the MIT License, see http://www.opensource.org/licenses/mit-license.php
|
||||
************************************************************************
|
||||
* B5500 emulator Magnetic Tape Drive web interface style sheet.
|
||||
************************************************************************
|
||||
* 2013-10-26 P.Kimpel
|
||||
* Original version, from B5500CardReader.css.
|
||||
***********************************************************************/
|
||||
|
||||
BODY {
|
||||
position: relative;
|
||||
background-color: black;
|
||||
margin: 4px}
|
||||
|
||||
DIV#MTDiv {
|
||||
position: relative;
|
||||
background-color: #666;
|
||||
width: 550px;
|
||||
height: 110px;
|
||||
border: 1px solid black;
|
||||
border-radius: 8px;
|
||||
padding: 0;
|
||||
vertical-align: top}
|
||||
|
||||
BUTTON.yellowButton {
|
||||
background-color: #990;
|
||||
color: black;
|
||||
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
|
||||
font-size: 8pt;
|
||||
font-weight: normal;
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: 1px solid #DDD;
|
||||
border-radius: 4px}
|
||||
|
||||
BUTTON.blackButton {
|
||||
background-color: black;
|
||||
color: white;
|
||||
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
|
||||
font-size: 8pt;
|
||||
font-weight: normal;
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: 1px solid #DDD;
|
||||
border-radius: 4px}
|
||||
|
||||
BUTTON.redButton {
|
||||
background-color: #900;
|
||||
color: white;
|
||||
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
|
||||
font-size: 8pt;
|
||||
font-weight: normal;
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: 1px solid #DDD;
|
||||
border-radius: 4px}
|
||||
|
||||
BUTTON.yellowLit {
|
||||
background-color: #FF0}
|
||||
|
||||
BUTTON.redLit {
|
||||
background-color: #F00}
|
||||
|
||||
SPAN.annunciator {
|
||||
position: absolute;
|
||||
color: #666;
|
||||
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
|
||||
font-size: 7pt;
|
||||
font-weight: bold}
|
||||
|
||||
SPAN.whiteLit {
|
||||
color: white}
|
||||
|
||||
#MTUnloadBtn {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 8px}
|
||||
|
||||
#MTLoadBtn {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 76px}
|
||||
|
||||
#MTLocalBtn {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 144px}
|
||||
|
||||
#MTRemoteBtn {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 212px;}
|
||||
|
||||
#MTWriteRingBtn {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 280px}
|
||||
|
||||
#MTRewindBtn {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 348px;}
|
||||
|
||||
IMG#MTReel {
|
||||
position: absolute;
|
||||
height: 40px;
|
||||
visibility: hidden;
|
||||
border-radius: 50%;
|
||||
top: 8px;
|
||||
left: 420px}
|
||||
|
||||
#MTUnloadedLight {
|
||||
top: 4px;
|
||||
right: 8px}
|
||||
|
||||
#MTAtBOTLight {
|
||||
top: 14px;
|
||||
right: 8px;}
|
||||
|
||||
#MTAtEOTLight {
|
||||
top: 24px;
|
||||
right: 8px}
|
||||
|
||||
#MTRewindingLight {
|
||||
top: 34px;
|
||||
right: 8px}
|
||||
|
||||
#MTFileSelector {
|
||||
position: absolute;
|
||||
border: 1px solid white;
|
||||
color: white;
|
||||
width: 530px;
|
||||
top: 54px;
|
||||
left: 8px}
|
||||
|
||||
#MTProgressBar {
|
||||
position: absolute;
|
||||
border: 1px solid white;
|
||||
width: 530px;
|
||||
top: 84px;
|
||||
left: 8px}
|
||||
35
webUI/B5500MagTapeDrive.html
Normal file
35
webUI/B5500MagTapeDrive.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<title>B5500 Emulator Magnetic Tape Drive</title>
|
||||
<meta name="Author" content="Nigel Williams & Paul Kimpel">
|
||||
<!-- 2013-10-26 Original version, cloned from B5500CardReader.html -->
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
|
||||
<meta http-equiv="Content-Script-Type" content="text/javascript">
|
||||
<meta http-equiv="Content-Style-Type" content="text/css">
|
||||
<link id=defaultStyleSheet rel=stylesheet type="text/css" href="B5500MagTapeDrive.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id=MTDiv>
|
||||
<button id=MTUnloadBtn class="blackButton">UNLOAD</button>
|
||||
<button id=MTLoadBtn class="blackButton">LOAD</button>
|
||||
<button id=MTLocalBtn class="yellowButton">LOCAL</button>
|
||||
<button id=MTRemoteBtn class="yellowButton">REMOTE</button>
|
||||
<button id=MTWriteRingBtn class="redButton">WRITE RING</button>
|
||||
<button id=MTRewindBtn class="blackButton">REWIND</button>
|
||||
|
||||
<img id=MTReel src="MagTapeReel.jpg">
|
||||
|
||||
<span id=MTUnloadedLight class=annunciator>UNLOADED</span>
|
||||
<span id=MTAtBOTLight class=annunciator>AT BOT</span>
|
||||
<span id=MTAtEOTLight class=annunciator>AT EOT</span>
|
||||
<span id=MTRewindingLight class=annunciator>REWINDING</span>
|
||||
|
||||
<input id=MTFileSelector type=file size=60>
|
||||
|
||||
<progress id=MTProgressBar min=0 max=100 value=0 title="Click to clear input hopper"></progress>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
938
webUI/B5500MagTapeDrive.js
Normal file
938
webUI/B5500MagTapeDrive.js
Normal file
@@ -0,0 +1,938 @@
|
||||
/***********************************************************************
|
||||
* retro-b5500/emulator B5500MagTapeDrive.js
|
||||
************************************************************************
|
||||
* Copyright (c) 2013, Nigel Williams and Paul Kimpel.
|
||||
* Licensed under the MIT License, see
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
************************************************************************
|
||||
* B5500 Magnetic Tape Drive Peripheral Unit module.
|
||||
*
|
||||
* Defines a magnetic tape drive peripheral unit type, emulating the
|
||||
* Burroughs B425 tape transport at 800 bits/inch.
|
||||
* This implementation supports READONLY operation for .bcd files ONLY.
|
||||
*
|
||||
************************************************************************
|
||||
* 2013-10-26 P.Kimpel
|
||||
* Original version, from B5500CardReader.js.
|
||||
***********************************************************************/
|
||||
"use strict";
|
||||
|
||||
/**************************************/
|
||||
function B5500MagTapeDrive(mnemonic, unitIndex, designate, statusChange, signal) {
|
||||
/* Constructor for the MagTapeDrive object */
|
||||
var that = this;
|
||||
var x = (mnemonic == "MTA" ? 30 : 60);
|
||||
|
||||
this.mnemonic = mnemonic; // Unit mnemonic
|
||||
this.unitIndex = unitIndex; // Ready-mask bit number
|
||||
this.designate = designate; // IOD unit designate number
|
||||
this.statusChange = statusChange; // external function to call for ready-status change
|
||||
this.signal = signal; // external function to call for special signals (not used here)
|
||||
|
||||
this.timer = null; // setCallback() token
|
||||
this.initiateStamp = 0; // timestamp of last initiation (set by IOUnit)
|
||||
|
||||
this.clear();
|
||||
|
||||
this.window = window.open("", mnemonic);
|
||||
if (this.window) {
|
||||
this.shutDown(); // destroy the previously-existing window
|
||||
this.window = null;
|
||||
}
|
||||
this.doc = null;
|
||||
this.window = window.open("../webUI/B5500MagTapeDrive.html", mnemonic,
|
||||
"scrollbars=no,resizable,width=560,height=120,left="+x+",top="+x);
|
||||
this.window.addEventListener("load", function windowLoad() {
|
||||
that.tapeDriveOnLoad();
|
||||
}, false);
|
||||
|
||||
this.progressBar = null;
|
||||
this.reelIcon = null;
|
||||
}
|
||||
|
||||
// this.tapeState enumerations
|
||||
B5500MagTapeDrive.prototype.tapeUnloaded = 0;
|
||||
B5500MagTapeDrive.prototype.tapeLocal = 1;
|
||||
B5500MagTapeDrive.prototype.tapeRemote = 2;
|
||||
|
||||
B5500MagTapeDrive.prototype.density = 800;
|
||||
// 800 bits/inch
|
||||
B5500MagTapeDrive.prototype.charsPerSec = 72000;
|
||||
// B425, 90 inches/sec @ 800 bits/inch
|
||||
B5500MagTapeDrive.prototype.gapLength = 0.75;
|
||||
// inter-block blank tape gap [inches]
|
||||
B5500MagTapeDrive.prototype.startStopTime = 0.0045 + 0.0042;
|
||||
// tape start+stop time [sec]
|
||||
B5500MagTapeDrive.prototype.rewindSpeed = 320;
|
||||
// rewind speed [inches/sec]
|
||||
B5500MagTapeDrive.prototype.maxTapeLength = 2400*12;
|
||||
// max tape length on reel [inches]
|
||||
B5500MagTapeDrive.prototype.maxBlankFrames = 9*12*B5500MagTapeDrive.prototype.density;
|
||||
// max blank tape length, 9 feet [frames]
|
||||
B5500MagTapeDrive.prototype.bcdTapeMark = 0x8F;
|
||||
// .bcd image EOF code
|
||||
B5500MagTapeDrive.prototype.reelCircumference = 10*3.14159;
|
||||
// max circumference of tape [inches]
|
||||
|
||||
B5500MagTapeDrive.prototype.cardFilter = [ // Filter ASCII character values to valid BIC ones
|
||||
0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F, // 00-0F
|
||||
0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F, // 10-1F
|
||||
0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x3F,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F, // 20-2F
|
||||
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F, // 30-3F
|
||||
0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, // 40-4F
|
||||
0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x3F,0x5D,0x3F,0x3F, // 50-5F
|
||||
0x3F,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, // 60-6F
|
||||
0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x7B,0x7C,0x7D,0x7E,0x3F]; // 70-7F
|
||||
|
||||
B5500MagTapeDrive.prototype.bcdXlateInOdd = [ // Translate odd parity BIC to ASCII
|
||||
0xFF,0x31,0x32,0xFF,0x34,0xFF,0xFF,0x37,0x38,0xFF,0xFF,0x40,0xFF,0x3A,0x3E,0xFF,
|
||||
0x2B,0xFF,0xFF,0x43,0xFF,0x45,0x46,0xFF,0xFF,0x49,0x2E,0xFF,0x26,0xFF,0xFF,0x7E,
|
||||
0x7C,0xFF,0xFF,0x4C,0xFF,0x4E,0x4F,0xFF,0xFF,0x52,0x24,0xFF,0x2D,0xFF,0xFF,0x7B,
|
||||
0xFF,0x2F,0x53,0xFF,0x55,0xFF,0xFF,0x58,0x59,0xFF,0xFF,0x25,0xFF,0x3D,0x5D,0xFF,
|
||||
0x30,0xFF,0xFF,0x33,0xFF,0x35,0x36,0xFF,0xFF,0x39,0x23,0xFF,0x3F,0xFF,0xFF,0x7D,
|
||||
0xFF,0x41,0x42,0xFF,0x44,0xFF,0xFF,0x47,0x48,0xFF,0xFF,0x5B,0xFF,0x28,0x3C,0xFF,
|
||||
0xFF,0x4A,0x4B,0xFF,0x4D,0xFF,0xFF,0x50,0x51,0xFF,0xFF,0x2A,0xFF,0x29,0x3B,0xFF,
|
||||
0x20,0xFF,0xFF,0x54,0xFF,0x56,0x57,0xFF,0xFF,0x5A,0x2C,0xFF,0x21,0xFF,0xFF,0x22]
|
||||
|
||||
B5500MagTapeDrive.prototype.bcdXlateInEven = [ // Translate even parity BCL to ASCII
|
||||
0x30,0xFF,0xFF,0x33,0xFF,0x35,0x36,0xFF,0xFF,0x39,0x23,0xFF,0x3F,0xFF,0xFF,0x7D, // 00-0F
|
||||
0xFF,0x41,0x42,0xFF,0x44,0xFF,0xFF,0x47,0x48,0xFF,0xFF,0x5B,0xFF,0x28,0x3C,0xFF, // 10-1F
|
||||
0xFF,0x4A,0x4B,0xFF,0x4D,0xFF,0xFF,0x50,0x51,0xFF,0xFF,0x2A,0xFF,0x29,0x3B,0xFF, // 20-2F
|
||||
0x20,0xFF,0xFF,0x54,0xFF,0x56,0x57,0xFF,0xFF,0x5A,0x2C,0xFF,0x21,0xFF,0xFF,0x22, // 30-3F
|
||||
0xFF,0x31,0x32,0xFF,0x34,0xFF,0xFF,0x37,0x38,0xFF,0xFF,0x40,0xFF,0x3A,0x3E,0xFF, // 40-4F
|
||||
0x2B,0xFF,0xFF,0x43,0xFF,0x45,0x46,0xFF,0xFF,0x49,0x2E,0xFF,0x26,0xFF,0xFF,0x7E, // 50-5F
|
||||
0x7C,0xFF,0xFF,0x4C,0xFF,0x4E,0x4F,0xFF,0xFF,0x52,0x24,0xFF,0x2D,0xFF,0xFF,0x7B, // 60-6F
|
||||
0xFF,0x2F,0x53,0xFF,0x55,0xFF,0xFF,0x58,0x59,0xFF,0xFF,0x25,0xFF,0x3D,0x5D,0xFF]; // 70-7F
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.$$ = function $$(e) {
|
||||
return this.doc.getElementById(e);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.clear = function clear() {
|
||||
/* Initializes (and if necessary, creates) the reader unit state */
|
||||
|
||||
this.ready = false; // ready status
|
||||
this.busy = false; // busy status
|
||||
|
||||
this.errorMask = 0; // error mask for finish()
|
||||
this.finish = null; // external function to call for I/O completion
|
||||
|
||||
this.image = null; // Tape drive "reel of tape"
|
||||
this.imgLength = 0; // Current input buffer length (characters)
|
||||
this.imgIndex = 0; // 0-relative offset to next tape block to be read
|
||||
|
||||
this.tapeState = this.tapeUnloaded; // tape drive state
|
||||
this.angle = 0; // current rotation angle of reel image [degrees]
|
||||
this.tapeInches = 0; // number of inches up-tape
|
||||
this.writeRing = false; // true if write ring is present and tape is writable
|
||||
this.atBOT = false; // true if tape at BOT
|
||||
this.atEOT = false; // true if tape at EOT
|
||||
|
||||
this.buffer = null; // IOUnit buffer
|
||||
this.bufLength = 0; // IOUnit buffer length
|
||||
this.bufIndex = 0; // IOUnit buffer current offset
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.hasClass = function hasClass(e, name) {
|
||||
/* returns true if element "e" has class "name" in its class list */
|
||||
var classes = e.className;
|
||||
|
||||
if (!e) {
|
||||
return false;
|
||||
} else if (classes == name) {
|
||||
return true;
|
||||
} else {
|
||||
return (classes.search("\\b" + name + "\\b") >= 0);
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.addClass = function addClass(e, name) {
|
||||
/* Adds a class "name" to the element "e"s class list */
|
||||
|
||||
if (!this.hasClass(e, name)) {
|
||||
e.className += (" " + name);
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.removeClass = function removeClass(e, name) {
|
||||
/* Removes the class "name" from the element "e"s class list */
|
||||
|
||||
e.className = e.className.replace(new RegExp("\\b" + name + "\\b\\s*", "g"), "");
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.spinReel = function spinReel(inches) {
|
||||
/* Rotates the reel image icon an appropriate amount based on the number of
|
||||
inches of tape movement */
|
||||
var circumference = this.reelCircumference*(1 - this.tapeInches/this.maxTapeLength/2);
|
||||
var angle = inches/circumference*360;
|
||||
|
||||
if (angle >= 33) {
|
||||
angle = 33;
|
||||
} else if (angle < -33) {
|
||||
angle = -33;
|
||||
}
|
||||
|
||||
this.angle = (this.angle + angle)%360;
|
||||
this.reelIcon.style.transform = "rotate(" + this.angle.toFixed(0) + "deg)";
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.setAtBOT = function setAtBOT(atBOT) {
|
||||
/* Controls the ready-state of the tape drive */
|
||||
|
||||
if (atBOT ^ this.atBOT) {
|
||||
this.atBOT = atBOT;
|
||||
if (atBOT) {
|
||||
this.imgIndex = 0;
|
||||
this.tapeInches = 0;
|
||||
this.addClass(this.$$("MTAtBOTLight"), "whiteLit");
|
||||
this.reelIcon.style.transform = "rotate(0deg)";
|
||||
} else {
|
||||
this.removeClass(this.$$("MTAtBOTLight"), "whiteLit");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.setAtEOT = function setAtEOT(atEOT) {
|
||||
/* Controls the ready-state of the tape drive */
|
||||
|
||||
if (atEOT ^ this.atEOT) {
|
||||
this.atEOT = atEOT;
|
||||
if (atEOT) {
|
||||
this.imgIndex = this.imgLength;
|
||||
this.addClass(this.$$("MTAtEOTLight"), "whiteLit");
|
||||
} else {
|
||||
this.removeClass(this.$$("MTAtEOTLight"), "whiteLit");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.setTapeUnloaded = function setTapeUnloaded() {
|
||||
/* Controls the loaded/unloaded-state of the tape drive */
|
||||
|
||||
if (this.tapeState != this.tapeRemote) {
|
||||
this.tapeState = this.tapeUnloaded;
|
||||
this.image = null; // release the tape image to GC
|
||||
this.imgIndex = this.imgLength = 0;
|
||||
this.writeRing = false;
|
||||
this.ready = false;
|
||||
this.statusChange(0);
|
||||
this.$$("MTUnloadBtn").disabled = true;
|
||||
this.$$("MTLoadBtn").disabled = false;
|
||||
this.$$("MTLocalBtn").disabled = true;
|
||||
this.$$("MTRemoteBtn").disabled = true;
|
||||
this.$$("MTRewindBtn").disabled = true;
|
||||
this.$$("MTWriteRingBtn").disabled = true;
|
||||
this.$$("MTFileSelector").disabled = false;
|
||||
this.$$("MTFileSelector").value = null;
|
||||
this.removeClass(this.$$("MTRemoteBtn"), "yellowLit");
|
||||
this.addClass(this.$$("MTLocalBtn"), "yellowLit");
|
||||
this.removeClass(this.$$("MTWriteRingBtn"), "redLit");
|
||||
this.addClass(this.$$("MTUnloadedLight"), "whiteLit");
|
||||
this.progressBar.value = 0;
|
||||
this.setAtBOT(false);
|
||||
this.setAtEOT(false);
|
||||
this.reelIcon.style.visibility = "hidden";
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.setTapeRemote = function setTapeRemote(ready) {
|
||||
/* Controls the ready-state of the tape drive */
|
||||
|
||||
if (this.tapeState != this.tapeUnloaded) {
|
||||
this.$$("MTLoadBtn").disabled = true;
|
||||
this.$$("MTUnloadBtn").disabled = ready;
|
||||
this.$$("MTLocalBtn").disabled = !ready;
|
||||
this.$$("MTRemoteBtn").disabled = ready;
|
||||
this.$$("MTWriteRingBtn").disabled = false;
|
||||
this.$$("MTRewindBtn").disabled = ready;
|
||||
this.$$("MTFileSelector").disabled = true;
|
||||
this.ready = ready;
|
||||
if (ready) {
|
||||
this.tapeState = this.tapeRemote;
|
||||
this.statusChange(1);
|
||||
this.removeClass(this.$$("MTLocalBtn"), "yellowLit");
|
||||
this.addClass(this.$$("MTRemoteBtn"), "yellowLit");
|
||||
} else {
|
||||
this.tapeState = this.tapeLocal;
|
||||
this.statusChange(0);
|
||||
this.removeClass(this.$$("MTRemoteBtn"), "yellowLit");
|
||||
this.addClass(this.$$("MTLocalBtn"), "yellowLit");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.setWriteRing = function setWriteRing(writeRing) {
|
||||
/* Controls the write-ring (write-enabled state of the tape drive. In Local state,
|
||||
writable status can be set or reset; in Remote state, it can only be reset */
|
||||
|
||||
switch (this.tapeState) {
|
||||
case this.tapeLocal:
|
||||
this.writeRing = writeRing;
|
||||
if (writeRing) {
|
||||
this.addClass(this.$$("MTWriteRingBtn"), "redLit");
|
||||
} else {
|
||||
this.removeClass(this.$$("MTWriteRingBtn"), "redLit");
|
||||
}
|
||||
break;
|
||||
case this.tapeRemote:
|
||||
if (this.writeRing && !writeRing) {
|
||||
this.writeRing = false;
|
||||
this.removeClass(this.$$("MTWriteRingBtn"), "redLit");
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.tapeRewind = function tapeRewind(makeReady) {
|
||||
/* Rewinds the tape. Makes the drive not-ready and delays for an appropriate amount
|
||||
of time depending on how far up-tape we are. If makeReady is true, then readies
|
||||
the unit again when the rewind is complete (valid only from this.rewind()) */
|
||||
var inches;
|
||||
var inchFactor = this.imgIndex/this.tapeInches;
|
||||
var lastStamp = new Date().getTime();
|
||||
|
||||
function rewindDelay() {
|
||||
var stamp = new Date().getTime();
|
||||
var interval = stamp - lastStamp;
|
||||
|
||||
if (this.tapeInches > 0) {
|
||||
inches = interval/1000*this.rewindSpeed + 1;
|
||||
this.tapeInches -= inches;
|
||||
this.spinReel(-inches);
|
||||
this.progressBar.value = this.imgLength - this.tapeInches*inchFactor;
|
||||
lastStamp = stamp;
|
||||
this.timer = setCallback(rewindDelay, this, 30);
|
||||
} else {
|
||||
this.busy = false;
|
||||
this.setAtBOT(true);
|
||||
this.progressBar.value = this.imgLength;
|
||||
this.removeClass(this.$$("MTRewindingLight"), "whiteLit");
|
||||
if (makeReady) {
|
||||
this.ready = true;
|
||||
this.statusChange(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.tapeState != this.tapeUnloaded) {
|
||||
this.busy = true;
|
||||
this.ready = false;
|
||||
this.statusChange(0);
|
||||
this.setAtEOT(false);
|
||||
this.addClass(this.$$("MTRewindingLight"), "whiteLit");
|
||||
this.timer = setCallback(rewindDelay, this,
|
||||
this.startStopTime*1000 + this.initiateStamp - new Date().getTime());
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.MTUnloadBtn_onclick = function MTUnloadBtn_onclick(ev) {
|
||||
/* Handle the click event for the UNLOAD button */
|
||||
|
||||
this.setTapeUnloaded();
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.MTLoadBtn_onclick = function MTLoadBtn_onclick(ev) {
|
||||
/* Handle the click event for the LOAD button */
|
||||
var ck = new MouseEvent("click", {cancelable:true, bubbles:false, detail:{}});
|
||||
|
||||
this.$$("MTFileSelector").value = null; // reset the control so the same file can be reloaded
|
||||
this.$$("MTFileSelector").dispatchEvent(ck);// click the file selector's Browse... button
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.MTRemoteBtn_onclick = function MTRemoteBtn_onclick(ev) {
|
||||
/* Handle the click event for the REMOTE button */
|
||||
|
||||
this.setTapeRemote(true);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.MTLocalBtn_onclick = function MTLocalBtn_onclick(ev) {
|
||||
/* Handle the click event for the LOCAL button */
|
||||
|
||||
this.setTapeRemote(false);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.MTWriteRingBtn_onclick = function MTWriteRingBtn_onclick(ev) {
|
||||
/* Handle the click event for the WRITE RING button */
|
||||
|
||||
this.setWriteRing(!this.writeRing);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.MTRewindBtn_onclick = function MTRewindBtn(ev) {
|
||||
/* Handle the click event for the REWIND button */
|
||||
|
||||
this.tapeRewind(false);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.fileSelector_onChange = function fileSelector_onChange(ev) {
|
||||
/* Handle the <input type=file> onchange event when a file is selected. Loads the
|
||||
file and puts the drive in Local state */
|
||||
var tape;
|
||||
var f = ev.target.files[0];
|
||||
var that = this;
|
||||
|
||||
function fileLoader_onLoad(ev) {
|
||||
/* Handle the onload event for the ArrayBuffer FileReader */
|
||||
|
||||
that.image = new Uint8Array(ev.target.result);
|
||||
that.imgIndex = 0;
|
||||
that.imgLength = that.image.length;
|
||||
that.progressBar.value = that.imgLength;
|
||||
that.progressBar.max = that.imgLength;
|
||||
that.removeClass(that.$$("MTUnloadedLight"), "whiteLit");
|
||||
that.tapeState = that.tapeLocal;// setTapeRemote() requires it not to be unloaded
|
||||
that.setAtBOT(true);
|
||||
that.setAtEOT(false);
|
||||
that.setTapeRemote(false);
|
||||
that.setWriteRing(false); // read-only for now...
|
||||
that.reelIcon.style.visibility = "visible";
|
||||
}
|
||||
|
||||
tape = new FileReader();
|
||||
tape.onload = fileLoader_onLoad;
|
||||
tape.readAsArrayBuffer(f);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.buildErrorMask = function buildErrorMask(chars) {
|
||||
/* Constructs the final error mask from this.errorMask, the number of residual
|
||||
characters in the last word, and the current drive state */
|
||||
var mask = this.errorMask & 0x01FC7FFF; // clear out the char count bits
|
||||
|
||||
mask |= (chars & 0x07) << 15;
|
||||
if (this.atBOT) {
|
||||
mask |= 0x80000; // tape at BOT
|
||||
} else if (this.atEOT) {
|
||||
mask |= 0x40000; // tape at EOT
|
||||
}
|
||||
this.errorMask = mask;
|
||||
return mask;
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.bcdSpaceForward = function bcdSpaceForward(checkEOF) {
|
||||
/* Spaces over the next block (or the remainder of the current block) from the
|
||||
.bcd tape (this.image) in a forward direction. No data transfer to the IOUnit buffer
|
||||
takes place, and no parity detection is made. A block is terminated when the next
|
||||
frame has its high-order bit set, or the end of the tape image data is reached.
|
||||
checkEOF true=> check for an EOF frame (i.e., we're not spacing the rest of a block)
|
||||
Exits with the image index pointing to the first frame of the next block (or beyond
|
||||
the end of the image blob if at EOT) */
|
||||
var blankCount = 0; // blank tape frame counter
|
||||
var c; // current character (tape image frame)
|
||||
var image = this.image; // tape image
|
||||
var imgLength = this.imgLength; // tape image length
|
||||
var imgIndex = this.imgIndex; // current tape image offset
|
||||
|
||||
if (imgIndex < imgLength) {
|
||||
if (this.atBOT) {
|
||||
this.setAtBOT(false);
|
||||
}
|
||||
c = image[imgIndex];
|
||||
if (checkEOF && c == this.bcdTapeMark && (imgIndex+1 >= imgLength || image[imgIndex+1] >= 0x80)) {
|
||||
this.errorMask |= 0x20; // EOF
|
||||
imgIndex++;
|
||||
} else {
|
||||
do {
|
||||
if (c == 0x00) {
|
||||
if (++blankCount > this.maxBlankFrames) {
|
||||
this.errorMask |= 0x100000; // blank tape timeout
|
||||
break; // kill the read loop
|
||||
}
|
||||
} else {
|
||||
blankCount = 0;
|
||||
}
|
||||
if (++imgIndex < imgLength) {
|
||||
c = image[imgIndex]; // get next char frame
|
||||
} else {
|
||||
break; // at end of tape, kill the read loop
|
||||
}
|
||||
} while (c < 0x80);
|
||||
}
|
||||
}
|
||||
this.imgIndex = imgIndex;
|
||||
if (imgIndex >= imgLength) {
|
||||
this.setAtEOT(true);
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.bcdSpaceBackward = function bcdSpaceBackward(checkEOF) {
|
||||
/* Spaces over the next block (or the remainder of the current block) from the
|
||||
.bcd tape (this.image) in a backward direction. No data transfer to the IOUnit buffer
|
||||
takes place, and no parity detection is made. A block is terminated when the next
|
||||
frame has its high-order bit set, or the beginning of the tape image data is reached.
|
||||
checkEOF true=> check for an EOF frame (i.e., we're not spacing the rest of a block)
|
||||
Exits with the image index pointing to the first frame of THIS block (or at 0 if
|
||||
at BOT). This arrangement allows changes between forward/reverse motion to work */
|
||||
var blankCount = 0; // blank tape frame counter
|
||||
var c; // current character (tape image frame)
|
||||
var image = this.image; // tape image
|
||||
var imgLength = this.imgLength; // tape image length
|
||||
var imgIndex = this.imgIndex; // current tape image offset
|
||||
|
||||
if (imgIndex <= 0) {
|
||||
this.setAtBOT(true);
|
||||
this.errorMask |= 0x100010; // set blank-tape and parity bits
|
||||
} else {
|
||||
if (this.atEOT) {
|
||||
this.setAtEOT(false);
|
||||
}
|
||||
c = image[--imgIndex];
|
||||
if (checkEOF && c == this.bcdTapeMark) {
|
||||
this.errorMask |= 0x20; // EOF
|
||||
} else {
|
||||
do {
|
||||
if (c == 0x00) {
|
||||
if (++blankCount > this.maxBlankFrames) {
|
||||
this.errorMask |= 0x100000; // blank tape timeout
|
||||
break; // kill the read loop
|
||||
}
|
||||
} else {
|
||||
blankCount = 0;
|
||||
}
|
||||
if (--imgIndex >= 0) {
|
||||
c = image[imgIndex]; // get next char frame
|
||||
} else {
|
||||
break; // at start of tape, kill the read loop
|
||||
}
|
||||
} while (c < 0x80);
|
||||
}
|
||||
if (imgIndex <= 0) {
|
||||
this.setAtBOT(true);
|
||||
}
|
||||
}
|
||||
this.imgIndex = imgIndex;
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.bcdReadForward = function bcdReadForward(oddParity) {
|
||||
/* Reads the next block from the .bcd tape (this.image) in a forward direction,
|
||||
translating the character frames to ANSI character codes based on the translation
|
||||
table "xlate". The translated data is stored in this.buffer. A block is terminated
|
||||
when the next frame has its high-order bit set, or the end of the tape image data
|
||||
is reached. The resulting buffer is always at least one character in length, unless
|
||||
the block is a tapeMark or the end of the data has been reached.
|
||||
oddParity 0=Alpha (even parity), 1=Binary (odd parity) read
|
||||
Exits with the image index pointing to the first frame of the next block (or beyond
|
||||
the end of the image blob if at EOT). Returns the number of characters read into the
|
||||
IOUnit buffer */
|
||||
var blankCount = 0; // blank tape frame counter
|
||||
var buffer = this.buffer; // IOUnit buffer
|
||||
var bufLength = this.bufLength // IOUnit buffer length
|
||||
var bufIndex = 0; // current IOUnit buffer offset
|
||||
var c; // current character (tape image frame)
|
||||
var cx; // current character translated to ASCII
|
||||
var image = this.image; // tape image
|
||||
var imgLength = this.imgLength; // tape image length
|
||||
var imgIndex = this.imgIndex; // current tape image offset
|
||||
var xlate = (oddParity ? this.bcdXlateInOdd : this.bcdXlateInEven);
|
||||
|
||||
if (imgIndex < imgLength) {
|
||||
if (this.atBOT) {
|
||||
this.setAtBOT(false);
|
||||
}
|
||||
c = image[imgIndex];
|
||||
if (c == this.bcdTapeMark && (imgIndex+1 >= imgLength || image[imgIndex+1] >= 0x80)) {
|
||||
this.errorMask |= 0x20; // EOF
|
||||
imgIndex++;
|
||||
} else {
|
||||
c &= 0x7F; // zap the start-of-block bit
|
||||
do {
|
||||
if (c == 0x00) {
|
||||
if (++blankCount > this.maxBlankFrames) {
|
||||
this.errorMask |= 0x100000; // blank tape timeout
|
||||
break; // kill the read loop
|
||||
} else if (++imgIndex < imgLength) {
|
||||
c = image[imgIndex]; // get next char frame
|
||||
} else {
|
||||
break; // at end of tape, kill the read loop
|
||||
}
|
||||
} else {
|
||||
blankCount = 0;
|
||||
cx = xlate[c];
|
||||
if (cx < 0xFF) {
|
||||
if (bufIndex < bufLength) {
|
||||
buffer[bufIndex++] = cx; // store the ANSI character
|
||||
if (++imgIndex < imgLength) {
|
||||
c = image[imgIndex]; // get next char frame
|
||||
} else {
|
||||
break; // at end of tape, kill the read loop
|
||||
}
|
||||
} else {
|
||||
this.imgIndex = imgIndex; // IOUnit buffer overflow
|
||||
this.bcdSpaceForward(false);
|
||||
imgIndex = this.imgIndex;
|
||||
break; // kill the read loop
|
||||
}
|
||||
} else {
|
||||
this.errorMask |= 0x10; // parity error
|
||||
this.imgIndex = imgIndex;
|
||||
this.bcdSpaceForward(false);
|
||||
imgIndex = this.imgIndex;
|
||||
break; // kill the read loop
|
||||
}
|
||||
}
|
||||
} while (c < 0x80);
|
||||
}
|
||||
}
|
||||
this.imgIndex = imgIndex;
|
||||
this.bufIndex = bufIndex;
|
||||
if (imgIndex >= imgLength) {
|
||||
this.setAtEOT(true);
|
||||
}
|
||||
return bufIndex;
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.bcdReadBackward = function bcdReadBackward(oddParity) {
|
||||
/* Reads the next block from the .bcd tape (this.image) in a backward direction,
|
||||
translating the character frames to ANSI character codes based on the translation
|
||||
table "xlate". The translated data is stored in this.buffer. A block is terminated
|
||||
when the next frame has its high-order bit set, or the beginning of the tape image
|
||||
data is reached. The resulting buffer is always at least one character in length,
|
||||
unless the block is a tapeMark or the end of the data has been reached.
|
||||
oddParity 0=Alpha (even parity), 1=Binary (odd parity) read
|
||||
Note that the characters are stored in this.buffer in ascending order as they are
|
||||
being read backwards; thus the buffer is in reverse order with respect to how the
|
||||
data will be stored in memory. The IOUnit will unravel this at finish.
|
||||
Exits with the image index pointing to the first frame of THIS block (or at 0 if
|
||||
at BOT). This arrangement allows changes between forward/reverse motion to work */
|
||||
var blankCount = 0; // blank tape frame counter
|
||||
var buffer = this.buffer; // IOUnit buffer
|
||||
var bufLength = this.bufLength // IOUnit buffer length
|
||||
var bufIndex = 0; // current IOUnit buffer offset
|
||||
var c; // current character (tape image frame)
|
||||
var cx; // current character translated to ASCII
|
||||
var image = this.image; // tape image
|
||||
var imgLength = this.imgLength; // tape image length
|
||||
var imgIndex = this.imgIndex; // current tape image offset
|
||||
var xlate = (oddParity ? this.bcdXlateInOdd : this.bcdXlateInEven);
|
||||
|
||||
if (imgIndex <= 0) {
|
||||
this.setAtBOT(true);
|
||||
this.errorMask |= 0x100010; // set blank-tape and parity bits
|
||||
} else {
|
||||
if (this.atEOT) {
|
||||
this.setAtEOT(false);
|
||||
}
|
||||
c = image[--imgIndex];
|
||||
if (c == this.bcdTapeMark) {
|
||||
this.errorMask |= 0x20; // EOF
|
||||
} else {
|
||||
do {
|
||||
if (c == 0x00) {
|
||||
if (++blankCount > this.maxBlankFrames) {
|
||||
this.errorMask |= 0x100000; // blank tape timeout
|
||||
break; // kill the read loop
|
||||
} else if (imgIndex > 0) {
|
||||
c = image[--imgIndex]; // get next char frame
|
||||
} else {
|
||||
break; // at end of tape, kill the read loop
|
||||
}
|
||||
} else {
|
||||
blankCount = 0;
|
||||
cx = xlate[c & 0x7F];
|
||||
if (cx < 0xFF) {
|
||||
if (bufIndex < bufLength) {
|
||||
buffer[bufIndex++] = cx; // store the ANSI character
|
||||
if (c >= 0x80) {
|
||||
break; // at start of block, kill the read loop
|
||||
} else if (imgIndex > 0) {
|
||||
c = image[--imgIndex]; // get next char frame
|
||||
} else {
|
||||
break; // at start of tape, kill the read loop
|
||||
}
|
||||
} else {
|
||||
this.imgIndex = imgIndex; // IOUnit buffer overflow
|
||||
this.bcdSpaceBackward(false);
|
||||
imgIndex = this.imgIndex;
|
||||
break; // kill the read loop
|
||||
}
|
||||
} else {
|
||||
this.errorMask |= 0x10; // parity error
|
||||
this.imgIndex = imgIndex;
|
||||
this.bcdSpaceBackward(false);
|
||||
imgIndex = this.imgIndex;
|
||||
break; // kill the read loop
|
||||
}
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
if (imgIndex <= 0) {
|
||||
this.setAtBOT(true);
|
||||
}
|
||||
}
|
||||
this.imgIndex = imgIndex;
|
||||
this.bufIndex = bufIndex;
|
||||
return bufIndex;
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.beforeUnload = function beforeUnload(ev) {
|
||||
var msg = "Closing this window will make the device unusable.\n" +
|
||||
"Suggest you stay on the page and minimize this window instead";
|
||||
|
||||
ev.preventDefault();
|
||||
ev.returnValue = msg;
|
||||
return msg;
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.tapeDriveOnLoad = function tapeDriveOnLoad() {
|
||||
/* Initializes the reader window and user interface */
|
||||
var that = this;
|
||||
|
||||
this.doc = this.window.document;
|
||||
this.doc.title = "retro-B5500 " + this.mnemonic;
|
||||
|
||||
this.progressBar = this.$$("MTProgressBar");
|
||||
this.reelIcon = this.$$("MTReel");
|
||||
|
||||
this.window.addEventListener("beforeunload", this.beforeUnload, false);
|
||||
|
||||
this.tapeState = this.tapeLocal; // setTapeUnloaded() requires it not to be unloaded
|
||||
this.setTapeUnloaded();
|
||||
|
||||
this.$$("MTUnloadBtn").addEventListener("click", function startClick(ev) {
|
||||
that.MTUnloadBtn_onclick(ev);
|
||||
}, false);
|
||||
|
||||
this.$$("MTLoadBtn").addEventListener("click", function stopClick(ev) {
|
||||
that.MTLoadBtn_onclick(ev);
|
||||
}, false);
|
||||
|
||||
this.$$("MTRemoteBtn").addEventListener("click", function startClick(ev) {
|
||||
that.MTRemoteBtn_onclick(ev);
|
||||
}, false);
|
||||
|
||||
this.$$("MTLocalBtn").addEventListener("click", function stopClick(ev) {
|
||||
that.MTLocalBtn_onclick(ev);
|
||||
}, false);
|
||||
|
||||
this.$$("MTWriteRingBtn").addEventListener("click", function eofClick(ev) {
|
||||
that.MTWriteRingBtn_onclick(ev);
|
||||
}, false);
|
||||
|
||||
this.$$("MTRewindBtn").addEventListener("click", function eofClick(ev) {
|
||||
that.MTRewindBtn_onclick(ev);
|
||||
}, false);
|
||||
|
||||
this.progressBar.addEventListener("click", function progressClick(ev) {
|
||||
that.MTProgressBar_onclick(ev);
|
||||
}, false);
|
||||
|
||||
this.$$("MTFileSelector").addEventListener("change", function fileSelectorChange(ev) {
|
||||
that.fileSelector_onChange(ev);
|
||||
}, false);
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.read = function read(finish, buffer, length, mode, control) {
|
||||
/* Initiates a read operation on the unit. If the drive is busy or not ready,
|
||||
returns those conditions. Otherwise, attempts to read the next block from the tape.
|
||||
mode 0=Alpha (even parity), 1=Binary (odd parity) read
|
||||
control 0=forward, 1=backward read
|
||||
At present, this supports only .bcd tape images */
|
||||
var count; // number of characters read into IOUnit buffer
|
||||
var imgCount = this.imgIndex; // number of characters passed on tape
|
||||
var inches = 0; // block length including gap [inches]
|
||||
var residue; // residual characters in last word read
|
||||
|
||||
this.errorMask = 0;
|
||||
if (this.busy) {
|
||||
finish(0x01, 0); // report unit busy
|
||||
} else if (!this.ready) {
|
||||
finish(0x04, 0); // report unit not ready
|
||||
} else {
|
||||
this.busy = true;
|
||||
this.buffer = buffer;
|
||||
this.bufLength = length;
|
||||
this.bufIndex = 0;
|
||||
|
||||
if (control) {
|
||||
count = this.bcdReadBackward(mode);
|
||||
residue = 7 - count % 8;
|
||||
imgCount -= this.imgIndex;
|
||||
if (imgCount > 0) {
|
||||
inches = -imgCount/this.density - this.gapLength;
|
||||
}
|
||||
} else {
|
||||
count = this.bcdReadForward(mode);
|
||||
residue = count % 8;
|
||||
imgCount = this.imgIndex - imgCount;
|
||||
if (imgCount > 0) {
|
||||
inches = imgCount/this.density + this.gapLength;
|
||||
}
|
||||
}
|
||||
|
||||
this.tapeInches += inches;
|
||||
this.buildErrorMask(residue);
|
||||
this.timer = setCallback(function readDelay() {
|
||||
this.busy = false;
|
||||
finish(this.errorMask, count);
|
||||
}, this, (imgCount/this.charsPerSec + this.startStopTime)*1000 +
|
||||
this.initiateStamp - new Date().getTime());
|
||||
|
||||
this.spinReel(inches);
|
||||
if (this.imgIndex < this.imgLength) {
|
||||
this.progressBar.value = this.imgLength-this.imgIndex;
|
||||
} else {
|
||||
this.progressBar.value = 0;
|
||||
}
|
||||
this.buffer = null;
|
||||
}
|
||||
//console.log(this.mnemonic + " read: c=" + control + ", length=" + length + ", mode=" + mode +
|
||||
// ", count=" + count + ", inches=" + this.tapeInches +
|
||||
// ", index=" + this.imgIndex + ", mask=" + this.errorMask.toString(8));
|
||||
//console.log(String.fromCharCode.apply(null, buffer.subarray(0, 80)).substring(0, count));
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.space = function space(finish, length, control) {
|
||||
/* Initiates a space operation on the unit. If the drive is busy or not ready,
|
||||
returns those conditions. Otherwise, attempts to space over the next block
|
||||
from the tape. Parity errors are ignored.
|
||||
control 0=forward, 1=backward space
|
||||
At present, this supports only .bcd tape images */
|
||||
var imgCount = this.imgIndex; // number of characters passed on tape
|
||||
var inches = 0; // block length including gap [inches]
|
||||
|
||||
this.errorMask = 0;
|
||||
if (this.busy) {
|
||||
finish(0x01, 0); // report unit busy
|
||||
} else if (!this.ready) {
|
||||
finish(0x04, 0); // report unit not ready
|
||||
} else {
|
||||
this.busy = true;
|
||||
|
||||
if (control) {
|
||||
this.bcdSpaceBackward(true);
|
||||
imgCount -= this.imgIndex;
|
||||
if (imgCount > 0) {
|
||||
inches = -imgCount/this.density - this.gapLength;
|
||||
}
|
||||
} else {
|
||||
this.bcdSpaceForward(true);
|
||||
imgCount = this.imgIndex - imgCount;
|
||||
if (imgCount > 0) {
|
||||
inches = imgCount/this.density + this.gapLength;
|
||||
}
|
||||
}
|
||||
|
||||
this.tapeInches += inches;
|
||||
this.buildErrorMask(0);
|
||||
this.timer = setCallback(function readDelay() {
|
||||
this.busy = false;
|
||||
finish(this.errorMask, 0);
|
||||
}, this, (imgCount/this.charsPerSec + this.startStopTime)*1000 +
|
||||
this.initiateStamp - new Date().getTime());
|
||||
|
||||
this.spinReel(inches);
|
||||
if (this.imgIndex < this.imgLength) {
|
||||
this.progressBar.value = this.imgLength-this.imgIndex;
|
||||
} else {
|
||||
this.progressBar.value = 0;
|
||||
}
|
||||
}
|
||||
//console.log(this.mnemonic + " space: c=" + control + ", length=" + length +
|
||||
// ", count=" + imgCount + ", inches=" + this.tapeInches +
|
||||
// ", index=" + this.imgIndex + ", mask=" + this.errorMask.toString(8));
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.write = function write(finish, buffer, length, mode, control) {
|
||||
/* Initiates a write operation on the unit */
|
||||
|
||||
finish(0x04, 0); // report unit not ready
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.erase = function erase(finish, length) {
|
||||
/* Initiates an erase operation on the unit */
|
||||
|
||||
finish(0x04, 0); // report unit not ready
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.rewind = function rewind(finish) {
|
||||
/* Initiates a rewind operation on the unit. If the drive is busy or not ready,
|
||||
returns those conditions. Otherwise, makes the drive not-ready, delays for an
|
||||
appropriate amount of time depending on how far up-tape we are, then readies the
|
||||
unit again */
|
||||
|
||||
this.errorMask = 0;
|
||||
if (this.busy) {
|
||||
finish(0x01, 0); // report unit busy
|
||||
} else if (!this.ready) {
|
||||
finish(0x04, 0); // report unit not ready
|
||||
} else {
|
||||
this.tapeRewind(true);
|
||||
this.buildErrorMask(0);
|
||||
finish(this.errorMask, 0);
|
||||
}
|
||||
//console.log(this.mnemonic + " rewind: mask=" + this.errorMask.toString(8));
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.readCheck = function readCheck(finish, length, control) {
|
||||
/* Initiates a read check operation on the unit */
|
||||
|
||||
finish(0x04, 0); // report unit not ready
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.readInterrogate = function readInterrogate(finish, control) {
|
||||
/* Initiates a read interrogate operation on the unit */
|
||||
|
||||
finish(0x04, 0); // report unit not ready
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.writeInterrogate = function writeInterrogate(finish, control) {
|
||||
/* Initiates a write interrogate operation on the unit. This is actually a write
|
||||
of zero length, typically used to determine ready and BOT/EOT status for the drive */
|
||||
|
||||
this.errorMask = 0;
|
||||
if (this.busy) {
|
||||
finish(0x01, 0); // report unit busy
|
||||
} else if (!this.ready) {
|
||||
finish(0x04, 0); // report unit not ready
|
||||
} else {
|
||||
this.buildErrorMask(0, true);
|
||||
if (!this.writeRing) {
|
||||
this.errorMask |= 0x50; // RD bits 26 & 28 => no write ring
|
||||
}
|
||||
finish(this.errorMask, 0);
|
||||
}
|
||||
//console.log(this.mnemonic + " writeInterrogate: c=" + control + ", mask=" + this.errorMask.toString(8));
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.shutDown = function shutDown() {
|
||||
/* Shuts down the device */
|
||||
|
||||
if (this.timer) {
|
||||
clearCallback(this.timer);
|
||||
}
|
||||
this.window.removeEventListener("beforeunload", this.beforeUnload, false);
|
||||
this.window.close();
|
||||
};
|
||||
@@ -23,7 +23,7 @@ PRE {
|
||||
|
||||
DIV#SPODiv {
|
||||
position: relative;
|
||||
width: 750px;
|
||||
width: 680px;
|
||||
height: 500px;
|
||||
background-color: #FFC;
|
||||
border-radius: 16px;
|
||||
@@ -34,7 +34,7 @@ IFRAME#SPOUT {
|
||||
left: 16px;
|
||||
bottom: 16px;
|
||||
height: 468px;
|
||||
width: 560px;
|
||||
width: 490px;
|
||||
border: 1px solid black}
|
||||
|
||||
BODY.SPOUT {
|
||||
@@ -54,18 +54,6 @@ IMG#TeletypeLogo {
|
||||
left: auto;
|
||||
right: auto}
|
||||
|
||||
BUTTON.whiteButton {
|
||||
position: absolute;
|
||||
background-color: #CCC;
|
||||
color: black;
|
||||
font-family: Arial Rounded, Arial, Helvetica, sans-serif;
|
||||
font-size: 8pt;
|
||||
font-weight: bold;
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: 1px solid #DDD;
|
||||
border-radius: 4px}
|
||||
|
||||
BUTTON.greenButton {
|
||||
position: absolute;
|
||||
background-color: #060;
|
||||
@@ -75,7 +63,7 @@ BUTTON.greenButton {
|
||||
font-weight: bold;
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: 1px solid #DDD;
|
||||
border: 1px solid black;
|
||||
border-radius: 4px}
|
||||
|
||||
BUTTON.yellowButton {
|
||||
@@ -87,21 +75,15 @@ BUTTON.yellowButton {
|
||||
font-weight: bold;
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: 1px solid #DDD;
|
||||
border: 1px solid black;
|
||||
border-radius: 4px}
|
||||
|
||||
BUTTON.whiteLit {
|
||||
background-color: white}
|
||||
|
||||
BUTTON.greenLit {
|
||||
background-color: #0F0}
|
||||
|
||||
BUTTON.yellowLit {
|
||||
background-color: #FF0}
|
||||
|
||||
BUTTON.blackBorder {
|
||||
border: 1px solid black}
|
||||
|
||||
BUTTON#SPOReadyBtn {
|
||||
bottom: 224px;
|
||||
left: 0px}
|
||||
@@ -141,14 +123,3 @@ BUTTON#SPOBlank2Btn {
|
||||
BUTTON#SPOBlank3Btn {
|
||||
bottom: 0px;
|
||||
right: 0px}
|
||||
|
||||
.center {
|
||||
text-align: center}
|
||||
|
||||
.data {
|
||||
font-family: Courier New, Courier, monospace;
|
||||
text-align: left}
|
||||
|
||||
.number {
|
||||
font-family: Courier New, Courier, monospace;
|
||||
text-align: right}
|
||||
|
||||
@@ -15,16 +15,16 @@
|
||||
<iframe id=SPOUT scrolling=auto></iframe>
|
||||
<div id=SPOControlsDiv>
|
||||
<img id=TeletypeLogo src="TeletypeLogo.gif" alt="TeleType Model 33 KSR Logo">
|
||||
<button id=SPOReadyBtn class="yellowButton blackBorder">READY</button>
|
||||
<button id=SPOPowerBtn class="greenButton blackBorder greenLit">POWER</button>
|
||||
<button id=SPORemoteBtn class="yellowButton blackBorder">REMOTE</button>
|
||||
<button id=SPOLocalBtn class="yellowButton blackBorder yellowLit">LOCAL</button>
|
||||
<button id=SPOInputRequestBtn class="yellowButton blackBorder">INPUT REQUEST</button>
|
||||
<button id=SPOEndOfMessageBtn class="yellowButton blackBorder">END OF MESSAGE</button>
|
||||
<button id=SPOBlank1Btn class="yellowButton blackBorder"></button>
|
||||
<button id=SPOErrorBtn class="yellowButton blackBorder">ERROR</button>
|
||||
<button id=SPOBlank2Btn class="yellowButton blackBorder"></button>
|
||||
<button id=SPOBlank3Btn class="yellowButton blackBorder"></button>
|
||||
<button id=SPOReadyBtn class="yellowButton">READY</button>
|
||||
<button id=SPOPowerBtn class="greenButton greenLit">POWER</button>
|
||||
<button id=SPORemoteBtn class="yellowButton">REMOTE</button>
|
||||
<button id=SPOLocalBtn class="yellowButton yellowLit">LOCAL</button>
|
||||
<button id=SPOInputRequestBtn class="yellowButton">INPUT REQUEST</button>
|
||||
<button id=SPOEndOfMessageBtn class="yellowButton">END OF MESSAGE</button>
|
||||
<button id=SPOBlank1Btn class="yellowButton"></button>
|
||||
<button id=SPOErrorBtn class="yellowButton">ERROR</button>
|
||||
<button id=SPOBlank2Btn class="yellowButton"></button>
|
||||
<button id=SPOBlank3Btn class="yellowButton"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -37,14 +37,14 @@ function B5500SPOUnit(mnemonic, unitIndex, designate, statusChange, signal) {
|
||||
|
||||
this.window = window.open("", mnemonic);
|
||||
if (this.window) {
|
||||
this.shutDown(); // destroy the previously-existing window
|
||||
this.shutDown(); // destroy any previously-existing window
|
||||
this.window = null;
|
||||
}
|
||||
this.doc = null;
|
||||
this.paper = null;
|
||||
this.endOfPaper = null;
|
||||
this.window = window.open("/B5500/webUI/B5500SPOUnit.html", mnemonic,
|
||||
"scrollbars,resizable,width=758,height=508");
|
||||
this.window = window.open("../webUI/B5500SPOUnit.html", mnemonic,
|
||||
"scrollbars,resizable,width=688,height=508");
|
||||
this.window.moveTo(screen.availWidth-this.window.outerWidth, screen.availHeight-this.window.outerHeight);
|
||||
this.window.addEventListener("load", B5500CentralControl.bindMethod(B5500SPOUnit.prototype.spoOnload, this), false);
|
||||
}
|
||||
@@ -134,7 +134,6 @@ B5500SPOUnit.prototype.setLocal = function setLocal() {
|
||||
this.buffer = null;
|
||||
this.bufLength = 0;
|
||||
this.bufIndex = 0;
|
||||
this.printCol = 0;
|
||||
this.nextCharTime = new Date().getTime();
|
||||
this.finish = null;
|
||||
};
|
||||
@@ -145,6 +144,7 @@ B5500SPOUnit.prototype.setRemote = function setRemote() {
|
||||
|
||||
if (this.spoState == this.spoLocal) {
|
||||
this.spoState = this.spoRemote;
|
||||
this.spoLocalRequested = false;
|
||||
this.addClass(this.$$("SPORemoteBtn"), "yellowLit");
|
||||
this.removeClass(this.$$("SPOLocalBtn"), "yellowLit");
|
||||
this.statusChange(1);
|
||||
@@ -164,6 +164,7 @@ B5500SPOUnit.prototype.appendEmptyLine = function appendEmptyLine() {
|
||||
}
|
||||
this.endOfPaper.scrollIntoView();
|
||||
this.paper.appendChild(line);
|
||||
this.printCol = 0;
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
@@ -185,16 +186,19 @@ B5500SPOUnit.prototype.backspaceChar = function backspaceChar() {
|
||||
/**************************************/
|
||||
B5500SPOUnit.prototype.printChar = function printChar(c) {
|
||||
/* Echoes the character code "c" to the SPO printer */
|
||||
var line = this.paper.lastChild;
|
||||
var len = line.nodeValue.length;
|
||||
var line = this.paper.lastChild.nodeValue;
|
||||
var len = line.length;
|
||||
|
||||
if (len < 1) {
|
||||
line.nodeValue = String.fromCharCode(c);
|
||||
line = String.fromCharCode(c);
|
||||
this.printCol++;
|
||||
} else if (len < 72) {
|
||||
line.nodeValue += String.fromCharCode(c);
|
||||
line += String.fromCharCode(c);
|
||||
this.printCol++;
|
||||
} else {
|
||||
line.nodeValue = line.nodeValue.substring(0, 71) + String.fromCharCode(c);
|
||||
line = line.substring(0, 71) + String.fromCharCode(c);
|
||||
}
|
||||
this.paper.lastChild.nodeValue = line;
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
@@ -211,7 +215,6 @@ B5500SPOUnit.prototype.outputChar = function outputChar() {
|
||||
if (this.bufIndex < this.bufLength) {
|
||||
this.printChar(this.buffer[this.bufIndex]);
|
||||
this.bufIndex++;
|
||||
this.printCol++;
|
||||
this.outTimer = setCallback(this.outputChar, this, delay);
|
||||
} else { // set up for the final CR/LF
|
||||
this.printCol = 72;
|
||||
@@ -223,7 +226,6 @@ B5500SPOUnit.prototype.outputChar = function outputChar() {
|
||||
} else { // actually output the CR/LF
|
||||
this.appendEmptyLine();
|
||||
if (this.bufIndex < this.bufLength) {
|
||||
this.printCol = 0; // more characters to print after the CR/LF
|
||||
this.outTimer = setCallback(this.outputChar, this, delay);
|
||||
} else { // message text is exhausted
|
||||
this.finish(this.errorMask, this.bufLength); // report finish with any errors
|
||||
@@ -267,101 +269,89 @@ B5500SPOUnit.prototype.keyPress = function keyPress(ev) {
|
||||
either buffers the character for transmission to the I/O Unit, simply echos
|
||||
it to the printer, or ignores it altogether */
|
||||
var c = ev.charCode;
|
||||
var delay;
|
||||
var index = this.bufLength;
|
||||
var nextTime;
|
||||
var stamp = new Date().getTime();
|
||||
|
||||
if (this.nextCharTime > stamp) {
|
||||
nextTime = this.nextCharTime + this.charPeriod;
|
||||
} else {
|
||||
nextTime = stamp + this.charPeriod;
|
||||
}
|
||||
this.nextCharTime = nextTime;
|
||||
nextTime = (this.nextCharTime > stamp ? this.nextCharTime : stamp) + this.charPeriod;
|
||||
delay = nextTime - stamp;
|
||||
|
||||
if (this.spoState == this.spoInput) {
|
||||
if (c >= 32 && c < 126) {
|
||||
this.buffer[this.bufIndex++] = c = this.keyFilter[c];
|
||||
if (this.printCol < 72) {
|
||||
this.printCol++;
|
||||
}
|
||||
this.inTimer = setCallback(this.printChar, this, nextTime-stamp, c);
|
||||
this.inTimer = setCallback(this.printChar, this, delay, c);
|
||||
this.nextCharTime = nextTime;
|
||||
ev.preventDefault();
|
||||
}
|
||||
if (c == 126) { // "~" (B5500 group-mark)
|
||||
c = this.keyFilter[c];
|
||||
if (this.printCol < 72) {
|
||||
this.printCol++;
|
||||
}
|
||||
this.inTimer = setCallback(this.printChar, this, nextTime-stamp, c);
|
||||
this.inTimer = setCallback(this.printChar, this, delay, c);
|
||||
this.nextCharTime = nextTime + this.charPeriod;
|
||||
this.terminateInput();
|
||||
ev.preventDefault();
|
||||
}
|
||||
} else if (this.spoState == this.spoLocal) {
|
||||
if (c >= 32 && c <= 126) {
|
||||
c = this.keyFilter[c];
|
||||
this.inTimer = setCallback(this.printChar, this, nextTime-stamp, c);
|
||||
this.inTimer = setCallback(this.printChar, this, delay, c);
|
||||
this.nextCharTime = nextTime;
|
||||
ev.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
ev.preventDefault();
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
B5500SPOUnit.prototype.keyDown = function keyDown(ev) {
|
||||
/* Handles key-down events to capture ESC, BS, and Enter keystrokes */
|
||||
var c = ev.keyCode;
|
||||
var delay;
|
||||
var nextTime;
|
||||
var result = true;
|
||||
var stamp = new Date().getTime();
|
||||
|
||||
if (this.nextCharTime > stamp) {
|
||||
nextTime = this.nextCharTime + this.charPeriod;
|
||||
} else {
|
||||
nextTime = stamp + this.charPeriod;
|
||||
}
|
||||
nextTime = (this.nextCharTime > stamp ? this.nextCharTime : stamp) + this.charPeriod;
|
||||
delay = nextTime - stamp;
|
||||
|
||||
switch (c) {
|
||||
case 27: // ESC
|
||||
case 0x1B: // ESC
|
||||
switch (this.spoState) {
|
||||
case this.spoRemote:
|
||||
case this.spoOutput:
|
||||
this.addClass(this.$$("SPOInputRequestBtn"), "yellowLit");
|
||||
this.signal();
|
||||
result = false;
|
||||
ev.preventDefault();
|
||||
break;
|
||||
case this.spoInput:
|
||||
this.cancelInput();
|
||||
result = false;
|
||||
ev.preventDefault();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 8: // Backspace
|
||||
case 0x08: // Backspace
|
||||
switch (this.spoState) {
|
||||
case this.spoInput:
|
||||
case this.spoLocal:
|
||||
this.inTimer = setCallback(this.backspaceChar, this, nextTime-stamp);
|
||||
this.inTimer = setCallback(this.backspaceChar, this, delay);
|
||||
this.nextCharTime = nextTime;
|
||||
result = false;
|
||||
ev.preventDefault();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 13: // Enter
|
||||
case 0x0D: // Enter
|
||||
switch (this.spoState) {
|
||||
case this.spoInput:
|
||||
this.terminateInput();
|
||||
this.nextCharTime = nextTime;
|
||||
result = false;
|
||||
break
|
||||
ev.preventDefault();
|
||||
break;
|
||||
case this.spoLocal:
|
||||
this.inTimer = setCallback(this.appendEmptyLine, this, nextTime-stamp+this.charPeriod);
|
||||
this.inTimer = setCallback(this.appendEmptyLine, this, delay+this.charPeriod);
|
||||
this.nextCharTime = nextTime;
|
||||
result = false;
|
||||
ev.preventDefault();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
@@ -418,8 +408,10 @@ B5500SPOUnit.prototype.spoOnload = function spoOnload() {
|
||||
this.window.addEventListener("beforeunload", this.beforeUnload, false);
|
||||
|
||||
this.window.addEventListener("keypress", B5500CentralControl.bindMethod(B5500SPOUnit.prototype.keyPress, this), false);
|
||||
this.$$("SPOUT").contentDocument.body.addEventListener("keypress", B5500CentralControl.bindMethod(B5500SPOUnit.prototype.keyPress, this), false);
|
||||
|
||||
this.window.addEventListener("keydown", B5500CentralControl.bindMethod(B5500SPOUnit.prototype.keyDown, this), false);
|
||||
this.$$("SPOUT").contentDocument.body.addEventListener("keydown", B5500CentralControl.bindMethod(B5500SPOUnit.prototype.keyDown, this), false);
|
||||
|
||||
this.$$("SPORemoteBtn").addEventListener("click", B5500CentralControl.bindMethod(B5500SPOUnit.prototype.setRemote, this), false);
|
||||
|
||||
@@ -465,10 +457,9 @@ B5500SPOUnit.prototype.read = function read(finish, buffer, length, mode, contro
|
||||
this.buffer = buffer;
|
||||
this.bufLength = length;
|
||||
this.bufIndex = 0;
|
||||
this.printCol = 0;
|
||||
this.nextCharTime = new Date().getTime();
|
||||
this.finish = finish;
|
||||
this.window.focus();
|
||||
//this.window.focus(); // interferes with datacom terminal window
|
||||
break;
|
||||
case this.spoOutput:
|
||||
case this.spoInput:
|
||||
@@ -498,10 +489,9 @@ B5500SPOUnit.prototype.write = function write(finish, buffer, length, mode, cont
|
||||
this.buffer = buffer;
|
||||
this.bufLength = length;
|
||||
this.bufIndex = 0;
|
||||
this.printCol = 0;
|
||||
this.nextCharTime = new Date().getTime();
|
||||
this.nextCharTime = this.initiateStamp;
|
||||
this.finish = finish;
|
||||
this.window.focus();
|
||||
//this.window.focus(); // interferes with datacom terminal window
|
||||
this.outputChar(); // start the printing process
|
||||
break;
|
||||
case this.spoOutput:
|
||||
|
||||
@@ -113,11 +113,12 @@
|
||||
args: null,
|
||||
fcn: fcn,
|
||||
context: context || this,
|
||||
delay: delay,
|
||||
};
|
||||
|
||||
pendingCallbacks[cookieName] = thisCallback;
|
||||
if (arguments.length > 3) {
|
||||
thisCallback.args = Array.slice(arguments, 3);
|
||||
thisCallback.args = Array.prototype.slice.call(arguments, 3);
|
||||
}
|
||||
|
||||
if (delay < minTimeout) {
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
<script src="./B5500CardReader.js"></script>
|
||||
<script src="./B5500CardPunch.js"></script>
|
||||
<script src="./B5500DummyPrinter.js"></script>
|
||||
<script src="./B5500DatacomUnit.js"></script>
|
||||
<script src="./B5500MagTapeDrive.js"></script>
|
||||
|
||||
<script src="../emulator/B5500SystemConfiguration.js"></script>
|
||||
<script src="../emulator/B5500CentralControl.js"></script>
|
||||
@@ -33,6 +35,7 @@ var runningCycles = 1000; // Number of instructions per run-mode i
|
||||
var cc;
|
||||
var injected = false; // true if syllable manually injected into T
|
||||
var memAddr = 0x40; // @100
|
||||
var px; // reference to currently-displayed processor
|
||||
var running = false; // true if in run mode
|
||||
var runSilently = false; // true if system state is not to be refreshed after each step
|
||||
var stopAddress = 0; // run-until-address stop point
|
||||
@@ -534,18 +537,18 @@ function displayStack() {
|
||||
var valueID;
|
||||
var x;
|
||||
|
||||
displayOctal("AReg", cc.P1.A, 16);
|
||||
displayBIC("ARegBIC", cc.P1.A);
|
||||
displayNumber("ARegValue", cc.P1.A);
|
||||
$$("AROF").checked = (cc.P1.AROF != 0);
|
||||
displayOctal("AReg", px.A, 16);
|
||||
displayBIC("ARegBIC", px.A);
|
||||
displayNumber("ARegValue", px.A);
|
||||
$$("AROF").checked = (px.AROF != 0);
|
||||
|
||||
displayOctal("BReg", cc.P1.B, 16);
|
||||
displayBIC("BRegBIC", cc.P1.B);
|
||||
displayNumber("BRegValue", cc.P1.B);
|
||||
$$("BROF").checked = (cc.P1.BROF != 0);
|
||||
displayOctal("BReg", px.B, 16);
|
||||
displayBIC("BRegBIC", px.B);
|
||||
displayNumber("BRegValue", px.B);
|
||||
$$("BROF").checked = (px.BROF != 0);
|
||||
|
||||
for (x=0; x<=7; x++) {
|
||||
addr = cc.P1.S - x;
|
||||
addr = px.S - x;
|
||||
setText("SAddr" + x, padOctal(addr, 5));
|
||||
e = $$("SWord" + x);
|
||||
bicID = e.getAttribute("data-b55sd-bicID");
|
||||
@@ -582,45 +585,45 @@ function displaySyllable() {
|
||||
/* Decodes the syllable in the T register and formats it for display in the
|
||||
"TMnemonic" table cell */
|
||||
|
||||
setText("TMnemonic", decodeSyllable(cc.P1.T, cc.P1.CWMF, cc.P1.SALF, cc.P1.MSFF));
|
||||
setText("TMnemonic", decodeSyllable(px.T, px.CWMF, px.SALF, px.MSFF));
|
||||
}
|
||||
|
||||
function displayRegisters() {
|
||||
/* Displays the non-stack processor registers */
|
||||
|
||||
displayOctal("XReg", cc.P1.X, 13);
|
||||
displayOctal("CReg", cc.P1.C, 5);
|
||||
displayOctal("LReg", cc.P1.L, 1);
|
||||
setText("SylAddr", (cc.P1.C*4 + cc.P1.L).toString(8));
|
||||
displayOctal("PReg", cc.P1.P, 16);
|
||||
$$("PROF").checked = (cc.P1.PROF != 0);
|
||||
displayOctal("TReg", cc.P1.T, 4);
|
||||
$$("TROF").checked = (cc.P1.TROF != 0);
|
||||
displayOctal("XReg", px.X, 13);
|
||||
displayOctal("CReg", px.C, 5);
|
||||
displayOctal("LReg", px.L, 1);
|
||||
setText("SylAddr", (px.C*4 + px.L).toString(8));
|
||||
displayOctal("PReg", px.P, 16);
|
||||
$$("PROF").checked = (px.PROF != 0);
|
||||
displayOctal("TReg", px.T, 4);
|
||||
$$("TROF").checked = (px.TROF != 0);
|
||||
displaySyllable();
|
||||
displayOctal("EReg", cc.P1.E, 2);
|
||||
displayOctal("IReg", cc.P1.I, 3);
|
||||
if (cc.P1.I) {
|
||||
displayOctal("EReg", px.E, 2);
|
||||
displayOctal("IReg", px.I, 3);
|
||||
if (px.I) {
|
||||
addClass("IReg", "warn");
|
||||
} else {
|
||||
removeClass("IReg", "warn");
|
||||
}
|
||||
displayOctal("QReg", cc.P1.Q, 4);
|
||||
displayOctal("MReg", cc.P1.M, 5);
|
||||
displayOctal("GReg", cc.P1.G, 1);
|
||||
displayOctal("HReg", cc.P1.H, 1);
|
||||
displayOctal("SReg", cc.P1.S, 5);
|
||||
displayOctal("KReg", cc.P1.K, 1);
|
||||
displayOctal("VReg", cc.P1.V, 1);
|
||||
displayOctal("FReg", cc.P1.F, 5);
|
||||
displayOctal("RReg", cc.P1.R, 3);
|
||||
displayOctal("YReg", cc.P1.Y, 2);
|
||||
displayOctal("ZReg", cc.P1.Z, 2);
|
||||
displayOctal("NReg", cc.P1.N, 2);
|
||||
$$("NCSF").checked = (cc.P1.NCSF != 0);
|
||||
$$("CWMF").checked = (cc.P1.CWMF != 0);
|
||||
$$("MSFF").checked = (cc.P1.MSFF != 0);
|
||||
$$("SALF").checked = (cc.P1.SALF != 0);
|
||||
$$("VARF").checked = (cc.P1.VARF != 0);
|
||||
displayOctal("QReg", px.Q, 4);
|
||||
displayOctal("MReg", px.M, 5);
|
||||
displayOctal("GReg", px.G, 1);
|
||||
displayOctal("HReg", px.H, 1);
|
||||
displayOctal("SReg", px.S, 5);
|
||||
displayOctal("KReg", px.K, 1);
|
||||
displayOctal("VReg", px.V, 1);
|
||||
displayOctal("FReg", px.F, 5);
|
||||
displayOctal("RReg", px.R, 3);
|
||||
displayOctal("YReg", px.Y, 2);
|
||||
displayOctal("ZReg", px.Z, 2);
|
||||
displayOctal("NReg", px.N, 2);
|
||||
$$("NCSF").checked = (px.NCSF != 0);
|
||||
$$("CWMF").checked = (px.CWMF != 0);
|
||||
$$("MSFF").checked = (px.MSFF != 0);
|
||||
$$("SALF").checked = (px.SALF != 0);
|
||||
$$("VARF").checked = (px.VARF != 0);
|
||||
}
|
||||
|
||||
function displayCentralControl() {
|
||||
@@ -671,14 +674,14 @@ function goIt(ev) {
|
||||
P and T to set up for the execution of the instruction at that syllable
|
||||
address, and then advances C/L to the next syllable address */
|
||||
|
||||
cc.P1.loadPviaC();
|
||||
cc.P1.T = cc.P1.cc.fieldIsolate(cc.P1.P, cc.P1.L*12, 12);
|
||||
cc.P1.TROF = 1;
|
||||
if (cc.P1.L < 3) {
|
||||
cc.P1.L++;
|
||||
px.loadPviaC();
|
||||
px.T = px.cc.fieldIsolate(px.P, px.L*12, 12);
|
||||
px.TROF = 1;
|
||||
if (px.L < 3) {
|
||||
px.L++;
|
||||
} else {
|
||||
cc.P1.L = 0;
|
||||
cc.P1.C++;
|
||||
px.L = 0;
|
||||
px.C++;
|
||||
}
|
||||
displaySystemState();
|
||||
}
|
||||
@@ -690,16 +693,16 @@ function stepIt(ev) {
|
||||
var next;
|
||||
|
||||
ev.target.disabled = true;
|
||||
setText("ARegOrig", padOctal(cc.P1.A, 16));
|
||||
setText("BRegOrig", padOctal(cc.P1.B, 16));
|
||||
setText("ARegOrig", padOctal(px.A, 16));
|
||||
setText("BRegOrig", padOctal(px.B, 16));
|
||||
|
||||
if (injected) {
|
||||
injected = false;
|
||||
next = cc.P1.C*4 + cc.P1.L - 1; // back up the auto-L increment
|
||||
cc.P1.L = next%4;
|
||||
cc.P1.C = (next - cc.P1.L)/4;
|
||||
next = px.C*4 + px.L - 1; // back up the auto-L increment
|
||||
px.L = next%4;
|
||||
px.C = (next - px.L)/4;
|
||||
}
|
||||
cc.P1.step();
|
||||
px.step();
|
||||
displaySystemState();
|
||||
ev.target.disabled = false;
|
||||
}
|
||||
@@ -719,7 +722,7 @@ function runIt(ev) {
|
||||
var word;
|
||||
|
||||
do {
|
||||
cc.P1.step();
|
||||
px.step();
|
||||
|
||||
if (false) { // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< DEBUG <<<<<<<<<<
|
||||
addr = cc.memMod[0][149] % 0x8000 + 1; // MCP PRT @225: addr of PRT[1,*] DD
|
||||
@@ -755,14 +758,14 @@ function runIt(ev) {
|
||||
}
|
||||
} // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
||||
|
||||
if (cc.P1.S > 0x7FFF) {
|
||||
if (px.S > 0x7FFF) {
|
||||
alert("P1.S out of range");
|
||||
stopAddress = 0;
|
||||
}
|
||||
/***************************
|
||||
if (cc.P1.C < 0x40) {
|
||||
if (px.C < 0x40) {
|
||||
// There is an interrupt
|
||||
switch (cc.P1.C) {
|
||||
switch (px.C) {
|
||||
case 0x31: // P1 invalid address
|
||||
case 0x32: // P1 stack overflow
|
||||
case 0x3C: // P1 integer overflow
|
||||
@@ -776,9 +779,9 @@ function runIt(ev) {
|
||||
count = runningCycles;
|
||||
break; // exit loop to pick up events
|
||||
}
|
||||
} while (runSilently && stopAddress && cc.P1.C != stopAddress);
|
||||
} while (runSilently && stopAddress && px.C != stopAddress);
|
||||
|
||||
if (stopAddress && cc.P1.C != stopAddress) {
|
||||
if (stopAddress && px.C != stopAddress) {
|
||||
setCallback(syllabicate, this, 0);
|
||||
if (!runSilently) {
|
||||
displaySystemState();
|
||||
@@ -786,6 +789,7 @@ function runIt(ev) {
|
||||
} else if (isNaN(stopAddress)) {
|
||||
stopAddress = 0; // bad address
|
||||
stopAddr.style.backgroundColor = "red";
|
||||
$$("SelectProcessor").disabled = false;
|
||||
runBtn.value = "Run";
|
||||
running = false;
|
||||
cc.inhCCI03F = true;
|
||||
@@ -793,6 +797,7 @@ function runIt(ev) {
|
||||
stopAddress = saveStopAddress;
|
||||
stopAddr.style.backgroundColor = "";
|
||||
stopAddr.value = padOctal(stopAddress, stopAddr.maxLength);
|
||||
$$("SelectProcessor").disabled = false;
|
||||
runBtn.value = "Run";
|
||||
running = false;
|
||||
cc.inhCCI03F = true;
|
||||
@@ -807,6 +812,7 @@ function runIt(ev) {
|
||||
setText("BRegOrig", "");
|
||||
injected = false;
|
||||
stopAddr.style.backgroundColor = "green";
|
||||
$$("SelectProcessor").disabled = true;
|
||||
runBtn.value = "Stop";
|
||||
running = true;
|
||||
cc.inhCCI03F = !runSilently;
|
||||
@@ -894,7 +900,7 @@ function fileLoader_onLoad(ev) {
|
||||
var words = 0;
|
||||
|
||||
cc.clear();
|
||||
cc.P1.clear();
|
||||
px.clear();
|
||||
try {
|
||||
words = cc.loadTest(buf, addr);
|
||||
alert("File loaded: " + buf.byteLength + " bytes, " +
|
||||
@@ -904,7 +910,7 @@ function fileLoader_onLoad(ev) {
|
||||
alert("File load failed: " + e.toString());
|
||||
}
|
||||
if (words > 0) {
|
||||
cc.P1.preset(0x10); // execute from address @20
|
||||
px.preset(0x10); // execute from address @20
|
||||
displaySystemState();
|
||||
}
|
||||
}
|
||||
@@ -923,7 +929,9 @@ function hardwareLoad_onClick(ev) {
|
||||
var result;
|
||||
|
||||
cc.clear();
|
||||
cc.P1.clear();
|
||||
$$("SelectProcessor").selectedIndex = 0;
|
||||
px = cc.P1;
|
||||
px.clear();
|
||||
cc.cardLoadSelect = $$("CardLoadSelect").checked;
|
||||
result = cc.load(true);
|
||||
switch (result) {
|
||||
@@ -945,6 +953,24 @@ function hardwareLoad_onClick(ev) {
|
||||
}
|
||||
}
|
||||
|
||||
function selectProcessor_onChange(ev) {
|
||||
/* Handle onChange for the SelectProcessor list to change the actively-displayed
|
||||
processor state */
|
||||
var value = ev.target.value;
|
||||
|
||||
if (value == "1") {
|
||||
px = cc.P1;
|
||||
displaySystemState();
|
||||
} else if (value == "2") {
|
||||
if (cc.P2) {
|
||||
px = cc.P2;
|
||||
displaySystemState();
|
||||
} else {
|
||||
ev.target.selectedIndex = 0; // revert back to P1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkBrowser() {
|
||||
/* Checks whether this browser can support the necessary stuff */
|
||||
var missing = "";
|
||||
@@ -975,20 +1001,20 @@ function initialize() {
|
||||
};
|
||||
|
||||
$$("AReg").onchange = function(ev) {
|
||||
cc.P1.A = tos_onChange(ev, "ARegBIC", "ARegValue", "ARegOrig");
|
||||
cc.P1.AROF = 1;
|
||||
px.A = tos_onChange(ev, "ARegBIC", "ARegValue", "ARegOrig");
|
||||
px.AROF = 1;
|
||||
$$("AROF").checked = true;
|
||||
};
|
||||
$$("AROF").onclick = function(ev) {
|
||||
cc.P1.AROF = ff_onChange(ev);
|
||||
px.AROF = ff_onChange(ev);
|
||||
};
|
||||
$$("BReg").onchange = function(ev) {
|
||||
cc.P1.B = tos_onChange(ev, "BRegBIC", "BRegValue", "BRegOrig");
|
||||
cc.P1.BROF = 1;
|
||||
px.B = tos_onChange(ev, "BRegBIC", "BRegValue", "BRegOrig");
|
||||
px.BROF = 1;
|
||||
$$("BROF").checked = true;
|
||||
};
|
||||
$$("BROF").onclick = function(ev) {
|
||||
cc.P1.BROF = ff_onChange(ev);
|
||||
px.BROF = ff_onChange(ev);
|
||||
};
|
||||
|
||||
$$("MAddr").onchange = function(ev) {
|
||||
@@ -1001,98 +1027,98 @@ function initialize() {
|
||||
};
|
||||
|
||||
$$("XReg").onchange = function(ev) {
|
||||
cc.P1.X = reg_onChange(ev);
|
||||
px.X = reg_onChange(ev);
|
||||
};
|
||||
$$("CReg").onchange = function(ev) {
|
||||
cc.P1.C = reg_onChange(ev);
|
||||
setText("SylAddr", (cc.P1.C*4 + cc.P1.L).toString(8));
|
||||
px.C = reg_onChange(ev);
|
||||
setText("SylAddr", (px.C*4 + px.L).toString(8));
|
||||
};
|
||||
$$("LReg").onchange = function(ev) {
|
||||
cc.P1.L = reg_onChange(ev);
|
||||
setText("SylAddr", (cc.P1.C*4 + cc.P1.L).toString(8));
|
||||
px.L = reg_onChange(ev);
|
||||
setText("SylAddr", (px.C*4 + px.L).toString(8));
|
||||
};
|
||||
$$("PReg").onchange = function(ev) {
|
||||
cc.P1.P = reg_onChange(ev);
|
||||
px.P = reg_onChange(ev);
|
||||
};
|
||||
$$("PROF").onclick = function(ev) {
|
||||
cc.P1.PROF = ff_onChange(ev);
|
||||
px.PROF = ff_onChange(ev);
|
||||
};
|
||||
$$("TReg").onchange = function(ev) {
|
||||
|
||||
cc.P1.T = reg_onChange(ev);
|
||||
px.T = reg_onChange(ev);
|
||||
injected = true;
|
||||
displaySyllable();
|
||||
};
|
||||
$$("TROF").onclick = function(ev) {
|
||||
cc.P1.TROF = ff_onChange(ev);
|
||||
px.TROF = ff_onChange(ev);
|
||||
};
|
||||
$$("EReg").onchange = function(ev) {
|
||||
cc.P1.E = reg_onChange(ev);
|
||||
px.E = reg_onChange(ev);
|
||||
};
|
||||
$$("IReg").onchange = function(ev) {
|
||||
cc.P1.I = reg_onChange(ev);
|
||||
if (cc.P1.I) {
|
||||
px.I = reg_onChange(ev);
|
||||
if (px.I) {
|
||||
addClass("IReg", "warn");
|
||||
} else {
|
||||
removeClass("IReg", "warn");
|
||||
}
|
||||
};
|
||||
$$("QReg").onchange = function(ev) {
|
||||
cc.P1.Q = reg_onChange(ev);
|
||||
px.Q = reg_onChange(ev);
|
||||
};
|
||||
$$("MReg").onchange = function(ev) {
|
||||
cc.P1.M = reg_onChange(ev);
|
||||
px.M = reg_onChange(ev);
|
||||
};
|
||||
$$("GReg").onchange = function(ev) {
|
||||
cc.P1.G = reg_onChange(ev);
|
||||
px.G = reg_onChange(ev);
|
||||
};
|
||||
$$("HReg").onchange = function(ev) {
|
||||
cc.P1.H = reg_onChange(ev);
|
||||
px.H = reg_onChange(ev);
|
||||
};
|
||||
$$("SReg").onchange = function(ev) {
|
||||
var addr = reg_onChange(ev);
|
||||
|
||||
if (!isNaN(addr)) {
|
||||
cc.P1.S = addr & 0x7FFF;
|
||||
px.S = addr & 0x7FFF;
|
||||
displayStack();
|
||||
}
|
||||
};
|
||||
$$("KReg").onchange = function(ev) {
|
||||
cc.P1.K = reg_onChange(ev);
|
||||
px.K = reg_onChange(ev);
|
||||
};
|
||||
$$("VReg").onchange = function(ev) {
|
||||
cc.P1.V = reg_onChange(ev);
|
||||
px.V = reg_onChange(ev);
|
||||
};
|
||||
$$("FReg").onchange = function(ev) {
|
||||
cc.P1.F = reg_onChange(ev);
|
||||
px.F = reg_onChange(ev);
|
||||
};
|
||||
$$("RReg").onchange = function(ev) {
|
||||
cc.P1.R = reg_onChange(ev);
|
||||
px.R = reg_onChange(ev);
|
||||
};
|
||||
$$("YReg").onchange = function(ev) {
|
||||
cc.P1.Y = reg_onChange(ev);
|
||||
px.Y = reg_onChange(ev);
|
||||
};
|
||||
$$("ZReg").onchange = function(ev) {
|
||||
cc.P1.Z = reg_onChange(ev);
|
||||
px.Z = reg_onChange(ev);
|
||||
};
|
||||
$$("NReg").onchange = function(ev) {
|
||||
cc.P1.N = reg_onChange(ev);
|
||||
px.N = reg_onChange(ev);
|
||||
};
|
||||
|
||||
$$("NCSF").onclick = function(ev) {
|
||||
cc.P1.NCSF = ff_onChange(ev);
|
||||
px.NCSF = ff_onChange(ev);
|
||||
};
|
||||
$$("CWMF").onclick = function(ev) {
|
||||
cc.P1.CWMF = ff_onChange(ev);
|
||||
px.CWMF = ff_onChange(ev);
|
||||
};
|
||||
$$("MSFF").onclick = function(ev) {
|
||||
cc.P1.MSFF = ff_onChange(ev);
|
||||
px.MSFF = ff_onChange(ev);
|
||||
};
|
||||
$$("SALF").onclick = function(ev) {
|
||||
cc.P1.SALF = ff_onChange(ev);
|
||||
px.SALF = ff_onChange(ev);
|
||||
};
|
||||
$$("VARF").onclick = function(ev) {
|
||||
cc.P1.VARF = ff_onChange(ev);
|
||||
px.VARF = ff_onChange(ev);
|
||||
};
|
||||
|
||||
$$("SWord0").onchange = word_onChange;
|
||||
@@ -1120,24 +1146,26 @@ function initialize() {
|
||||
|
||||
$$("FileSelector").addEventListener("change", fileSelector_onChange, false);
|
||||
$$("HardwareLoad").addEventListener("click", hardwareLoad_onClick, false);
|
||||
$$("SelectProcessor").addEventListener("change", selectProcessor_onChange, false);
|
||||
$$("GoBtn").addEventListener("click", goIt, false);
|
||||
$$("StepBtn").addEventListener("click", stepIt, false);
|
||||
$$("RunBtn").addEventListener("click", runIt, false);
|
||||
$$("RunSilently").addEventListener("click", runSilently_onClick, false);
|
||||
|
||||
cc = new B5500CentralControl();
|
||||
cc = new B5500CentralControl(window);
|
||||
cc.powerOn();
|
||||
px = cc.P1; // set current processor reference
|
||||
|
||||
cc.P1.S = 0x40; // stack at @100
|
||||
cc.P1.R = 0x005; // PRT at @500 (R has addr div 64)
|
||||
px.S = 0x40; // stack at @100
|
||||
px.R = 0x005; // PRT at @500 (R has addr div 64)
|
||||
|
||||
cc.P1.C = 0x10; // execute from address @20
|
||||
cc.P1.loadPviaC();
|
||||
cc.P1.T = cc.fieldIsolate(cc.P1.P, 0, 12);
|
||||
cc.P1.TROF = 1;
|
||||
cc.P1.L = 1; // point to the next syllable
|
||||
px.C = 0x10; // execute from address @20
|
||||
px.loadPviaC();
|
||||
px.T = cc.fieldIsolate(px.P, 0, 12);
|
||||
px.TROF = 1;
|
||||
px.L = 1; // point to the next syllable
|
||||
|
||||
cc.P1.NCSF = 0; // initiate test in control state
|
||||
px.NCSF = 0; // initiate test in control state
|
||||
|
||||
displaySystemState();
|
||||
displayOctal("StopAddr", stopAddress, 5);
|
||||
@@ -1170,6 +1198,11 @@ window.onload = function() {
|
||||
<input id=HardwareLoad type=button value="Hardware Load">
|
||||
<input id=CardLoadSelect type=checkbox value="CardLoadSelect">
|
||||
<label for=CardLoadSelect>Card Load Select</label>
|
||||
|
||||
<select id=SelectProcessor>
|
||||
<option value=1 selected>Show P1
|
||||
<option value=2>Show P2
|
||||
</select>
|
||||
</p>
|
||||
|
||||
<table id=RegisterBank1 class="normal border">
|
||||
|
||||
BIN
webUI/MagTapeReel.jpg
Normal file
BIN
webUI/MagTapeReel.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.0 KiB |
Reference in New Issue
Block a user