1
0
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:
paul.kimpel@digm.com
2013-11-15 05:33:58 +00:00
parent 769c149d1f
commit 2260803c51
28 changed files with 3289 additions and 375 deletions

View File

@@ -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

View File

@@ -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);
}
};

View File

@@ -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

View File

@@ -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();

View File

@@ -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

View File

@@ -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
View 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 "&lt;";
case ">": return "&gt;";
case '"': return "&quot;";
case "&": return "&amp;";
case " ": return "&nbsp;"
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>

View File

@@ -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("&lt;");
@@ -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];

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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>

View 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}

View 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>
&nbsp;&nbsp;
<span id=NotReadyState class="annunciator">NR</span>&nbsp;&nbsp;
<span id=IdleState class="annunciator">IDLE</span>&nbsp;&nbsp;
<span id=ReadReadyState class="annunciator">RR</span>&nbsp;&nbsp;
<span id=WriteReadyState class="annunciator">WR</span>&nbsp;&nbsp;
<span id=InputBusyState class="annunciator">IBZ</span>&nbsp;&nbsp;
<span id=OutputBusyState class="annunciator">OBZ</span>&nbsp;&nbsp;
<span id=Abnormal class="annunciator">AB</span>&nbsp;&nbsp;
<span id=Interrupt class="annunciator">INT</span>&nbsp;&nbsp;
<span id=FullBuffer class="annunciator">FB</span>&nbsp;&nbsp;
Offset:
<span id=BufferOffset class="annunciator textLit">0</span>&nbsp;&nbsp;
Length:
<span id=BufferLength class="annunciator textLit">0</span>&nbsp;&nbsp;
Col:
<span id=PrintColumn class="annunciator textLit">0</span>&nbsp;&nbsp;
<!--
<br>Key:
<span id=CharCode class="annunciator textLit"></span>&nbsp;
<span id=KeyCode class="annunciator textLit"></span>
-->
</div>
<iframe id=TermOut scrolling=auto></iframe>
</body>
</html>

854
webUI/B5500DatacomUnit.js Normal file
View 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();
};

View File

@@ -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
View 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}

View 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
View 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();
};

View File

@@ -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}

View File

@@ -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>

View File

@@ -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:

View File

@@ -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) {

View File

@@ -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>
&nbsp;&nbsp;
<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

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB