mirror of
https://github.com/pkimpel/retro-b5500.git
synced 2026-02-12 11:17:29 +00:00
1187 lines
46 KiB
JavaScript
1187 lines
46 KiB
JavaScript
/***********************************************************************
|
|
* retro-b5500/emulator B5500Processor.js
|
|
************************************************************************
|
|
* Copyright (c) 2012, Nigel Williams and Paul Kimpel.
|
|
* Licensed under the MIT License, see http://www.opensource.org/licenses/mit-license.php
|
|
************************************************************************
|
|
* JavaScript object definition for the B5500 Processor (CPU) module.
|
|
************************************************************************
|
|
* 2012-06-03 P.Kimpel
|
|
* Original version, from thin air.
|
|
***********************************************************************/
|
|
|
|
/**************************************/
|
|
function B5500Processor() {
|
|
/* Constructor for the Processor module object */
|
|
|
|
this.scheduler = null; // Reference to current setTimeout id
|
|
this.accessor = { // Memory access control block
|
|
addr: 0, // Memory address
|
|
word: 0, // 48-bit data word
|
|
MAIL: 0, // Truthy if attempt to access @000-@777 in normal state
|
|
MPED: 0, // Truthy if memory parity error
|
|
MAED: 0}; // Truthy if memory address/inhibit error
|
|
|
|
this.schedule.that = this; // Establish context for when called from setTimeout()
|
|
|
|
this.clear(); // Create and initialize the processor state
|
|
}
|
|
|
|
/**************************************/
|
|
|
|
B5500Processor.prototype.timeSlice = 5000; // Standard run() timeslice, about 5ms (we hope)
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.clear = function() {
|
|
/* Initializes the processor state */
|
|
|
|
this.A = 0; // Top-of-stack register 1
|
|
this.AROF = 0; // A contents valid
|
|
this.B = 0; // Top-of-stack register 2
|
|
this.BROF = 0; // B contents valid
|
|
this.C = 0; // Current program instruction word address
|
|
this.CCCF = 0; // Clock-count control FF (maintenance only)
|
|
this.CWMF = 0; // Character/word mode FF (1=CM)
|
|
this.E = 0; // Memory access control register
|
|
this.EIHF = 0; // ??
|
|
this.F = 0; // Top MSCW/RCW stack address
|
|
this.G = 0; // Character index register for A
|
|
this.H = 0; // Bit index register for G (in A)
|
|
this.HLTF = 0; // Processor halt FF
|
|
this.I = 0; // Processor interrupt register
|
|
this.K = 0; // Character index register for B
|
|
this.L = 0; // Instruction syllable index in P
|
|
this.M = 0; // Memory address register (SI.w in CM)
|
|
this.MRAF = 0; // Memory read access FF
|
|
this.MROF = 0; // Memory read obtained FF
|
|
this.MSFF = 0; // Mark-stack FF (word mode: MSCW is pending RCW, physically also TFFF & Q12F)
|
|
this.MWOF = 0; // Memory write obtained FF
|
|
this.N = 0; // Octal shift counter for B
|
|
this.NCSF = 0; // Normal/control state FF (1=normal)
|
|
this.P = 0; // Current program instruction word register
|
|
this.PROF = 0; // P contents valid
|
|
this.Q = 0; // Misc. FFs (bits 1-9 only: Q07F=hardware-induced interrupt, Q09F=enable parallel adder for R-relative addressing)
|
|
this.R = 0; // High-order 9 bits of PRT base address (TALLY in char mode)
|
|
this.S = 0; // Top-of-stack memory address (DI.w in CM)
|
|
this.SALF = 0; // Program/subroutine state FF (1=subroutine)
|
|
this.T = 0; // Current program syllable register
|
|
this.TM = 0; // Temporary maintenance storage register
|
|
this.TROF = 0; // T contents valid
|
|
this.V = 0; // Bit index register for K (in B)
|
|
this.VARF = 0; // Variant-mode FF (enables full PRT indexing)
|
|
this.X = 0; // Mantissa extension for B (loop control in CM)
|
|
this.Y = 0; // Serial character register for A
|
|
this.Z = 0; // Serial character register for B
|
|
|
|
this.cycleCount = 0; // Current cycle count for this.run()
|
|
this.cycleLimit = 0; // Cycle limit for this.run()
|
|
this.totalCycles = 0; // Total cycles executed on this processor
|
|
this.procTime = 0; // Current processor running time, based on cycles executed
|
|
this.scheduleSlack = 0; // Total processor throttling delay, milliseconds
|
|
this.busy = false; // Proessor is running, not idle or halted
|
|
}
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.access = function(eValue) {
|
|
/* Access memory based on the E register. If the processor is in normal
|
|
state, it cannot access the first 512 words of memory => invalid address */
|
|
|
|
this.E = eValue; // Just to show the world what's happening
|
|
this.accessor.MAIL = (addr < 0x0200 && this.NCSF);
|
|
switch (eValue) {
|
|
case 0x02: // A = [S]
|
|
this.accessor.addr = this.S;
|
|
cc.fetch(this);
|
|
this.A = this.accessor.word;
|
|
this.AROF = 1;
|
|
break;
|
|
case 0x03: // B = [S]
|
|
this.accessor.addr = this.S;
|
|
cc.fetch(this);
|
|
this.B = this.accessor.word;
|
|
this.BROF = 1;
|
|
break;
|
|
case 0x04: // A = [M]
|
|
this.accessor.addr = this.M;
|
|
cc.fetch(this);
|
|
this.A = this.accessor.word;
|
|
this.AROF = 1;
|
|
break;
|
|
case 0x05: // B = [M]
|
|
this.accessor.addr = this.M;
|
|
cc.fetch(this);
|
|
this.B = this.accessor.word;
|
|
this.BROF = 1;
|
|
break;
|
|
case 0x06: // M = [M].[18:15]
|
|
this.accessor.addr = this.M;
|
|
cc.fetch(this);
|
|
this.M = (this.accessor.word >>> 15) & 0x7FFF;
|
|
break;
|
|
case 0x0A: // [S] = A
|
|
this.accessor.addr = this.S;
|
|
this.accessor.word = this.A;
|
|
cc.store(this);
|
|
break;
|
|
case 0x0B: // [S] = B
|
|
this.accessor.addr = this.S;
|
|
this.accessor.word = this.B;
|
|
cc.store(this);
|
|
break;
|
|
case 0x0C: // [M] = A
|
|
this.accessor.addr = this.M;
|
|
this.accessor.word = this.A;
|
|
cc.store(this);
|
|
break;
|
|
case 0x0D: // [M] = B
|
|
this.accessor.addr = this.M;
|
|
this.accessor.word = this.B;
|
|
cc.store(this);
|
|
break;
|
|
case 0x30: // P = [C]
|
|
this.accessor.addr = this.C;
|
|
cc.fetch(this);
|
|
this.P = this.accessor.word;
|
|
this.PROF = 1;
|
|
break;
|
|
default:
|
|
throw "Invalid E register value: " + eReg.toString(2);
|
|
break;
|
|
}
|
|
|
|
this.cycleCount += 6; // assume 6 us memory cycle time
|
|
if (this.accessor.MAED) {
|
|
this.I |= 0x02; // set I02F - memory address/inhibit error
|
|
if (this.NCSF || this !== cc.P1) {
|
|
cc.signalInterrupt();
|
|
} else {
|
|
this.busy = false; // P1 invalid address in control state stops the proc
|
|
}
|
|
} else if (this.accessor.MPED) {
|
|
this.I |= 0x01; // set I01F - memory parity error
|
|
if (this.NCSF || this !== cc.P1) {
|
|
cc.signalInterrupt();
|
|
} else {
|
|
this.busy = false; // P1 memory parity in control state stops the proc
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.adjustAEmpty = function() {
|
|
/* Adjusts the A register so that it is empty pushing the prior
|
|
contents of A into B and B into memory, as necessary. */
|
|
|
|
if (this.AROF} {
|
|
if (this.BROF) {
|
|
if ((this.S >>> 6) == this.R || !this.NCSF) {
|
|
this.I |= 0x04; // set I03F: stack overflow
|
|
cc.signalInterrupt();
|
|
} else {
|
|
this.S++;
|
|
this.access(0x0B); // [S] = B
|
|
}
|
|
}
|
|
this.B = this.A;
|
|
this.AROF = 0;
|
|
this.BROF = 1;
|
|
// else we're done -- A is already empty
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.adjustAFull = function() {
|
|
/* Adjusts the A register so that it is full, popping the contents of
|
|
B or [S] into A, as necessary. */
|
|
|
|
if (!this.AROF) {
|
|
if (this.BROF) {
|
|
this.A = this.B;
|
|
this.AROF = 1;
|
|
this.BROF = 0;
|
|
} else {
|
|
this.access(0x02); // A = [S]
|
|
this.S--;
|
|
}
|
|
// else we're done -- A is already full
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.adjustBEmpty = function() {
|
|
/* Adjusts the B register so that it is empty pushing the prior
|
|
contents of B into memory, as necessary. */
|
|
|
|
if (this.BROF) {
|
|
if ((this.S >>> 6) = this.R || !this.NCSF) {
|
|
this.I |= 0x04; // set I03F: stack overflow
|
|
cc.signalInterrupt();
|
|
} else {
|
|
this.S++;
|
|
this.access(0x0B); // [S] = B
|
|
}
|
|
// else we're done -- B is already empty
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.adjustBFull = function() {
|
|
/* Adjusts the B register so that it is full popping the contents of
|
|
[S] into B, as necessary. */
|
|
|
|
if (!this.BROF) {
|
|
this.access(0x03); // B = [S]
|
|
this.S--;
|
|
// else we're done -- B is already full
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
B5500Processor.storeForInterrupt = function(forTest) {
|
|
/* Implements the 3011=SFI operator and the parts of SFT that are
|
|
common to it. "forTest" implies use from SFT */
|
|
var forced = this.Q & 0x0040; // Q07F: Hardware-induced SFI syllable
|
|
var saveAROF = this.AROF;
|
|
var saveBROF = this.BROF;
|
|
var temp;
|
|
|
|
if (forced || forTest) {
|
|
this.NCSF = 0; // switch to control state
|
|
}
|
|
|
|
if (this.CWMF) {
|
|
temp = this.S; // get the correct TOS address from X
|
|
this.S = (this.X % 0x40000000) >>> 15;
|
|
this.X = this.X % 0x8000 +
|
|
temp * 0x8000 +
|
|
Math.floor(this.X / 0x40000000) * 0x40000000;
|
|
if (this.AROF || forTest) {
|
|
this.access(0x0A); // [S] = A
|
|
}
|
|
if (this.BROF || forTest) {
|
|
this.access(0x0B); // [S] = B
|
|
}
|
|
this.B = this.X + // store CM loop-control word
|
|
saveAROF * 0x200000000000 +
|
|
0xC00000000000;
|
|
this.access(0x0B); // [S] = B
|
|
} else {
|
|
if (this.BROF || forTest) {
|
|
this.access(0x0B); // [S] = B
|
|
}
|
|
if (this.AROF || forTest) {
|
|
this.access(0x0A); // [S] = A
|
|
}
|
|
}
|
|
this.B = this.M + // store interrupt control word (ICW)
|
|
this.N * 0x8000 +
|
|
this.VARF * 0x1000000 +
|
|
this.SALF * 0x40000000 +
|
|
this.MSFF * 0x80000000 +
|
|
this.R * 0x200000000 +
|
|
0xC00000000000;
|
|
this.access(0x0B); // [S] = B
|
|
|
|
this.B = this.C + // store interrupt return control word (IRCW)
|
|
this.F * 0x8000 +
|
|
this.K * 0x40000000 +
|
|
this.G * 0x200000000 +
|
|
this.L * 0x1000000000 +
|
|
this.V * 0x4000000000 +
|
|
this.H * 0x20000000000 +
|
|
saveBROF * 0x200000000000 +
|
|
0xC00000000000;
|
|
this.access(0x0B); // [S] = B
|
|
|
|
if (this.CWMF) {
|
|
temp = this.F; // if CM, get correct R value from last MSCW
|
|
this.F = this.S;
|
|
this.S = temp;
|
|
this.access(0x03); // B = [S]: get last RCW
|
|
this.S = ((this.B % 0x40000000) >>> 15) & 0x7FFF;
|
|
this.access(0x03); // B = [S]: get last MSCW
|
|
this.R = Math.Floor(this.B / 0x200000000) % 0x200;
|
|
this.S = this.F;
|
|
}
|
|
|
|
this.B = this.S + // store the initiate control word (INCW)
|
|
this.CWMF * 0x8000 +
|
|
0xC00000000000;
|
|
if (forTest) {
|
|
this.B += (this.TM & 0x1F) * 0x10000 +
|
|
this.Z * 0x400000 +
|
|
this.Y * 0x10000000 +
|
|
(this.Q & 0x1FF) * 0x400000000;
|
|
this.TM = 0;
|
|
this.MROF = 0;
|
|
this.MWOF = 0;
|
|
}
|
|
|
|
this.M = (this.R << 6) + 0x08; // store initiate word at R+@10
|
|
this.access(0x0D); // [M] = B
|
|
|
|
this.M = 0;
|
|
this.R = 0;
|
|
this.MSFF = 0;
|
|
this.SALF = 0;
|
|
this.BROF = 0;
|
|
this.AROF = 0;
|
|
if (forced) {
|
|
if (this === cc.P1) {
|
|
this.T = 0x89; // inject 0211=ITI into T register
|
|
} else {
|
|
this.T = 0; // idle the processor
|
|
this.TROF = 0;
|
|
this.PROF = 0;
|
|
this.busy = false;
|
|
cc.HP2F = 1;
|
|
cc.P2BF = 0;
|
|
if (cc.P2.scheduler) {
|
|
cancelTimeout(cc.P2.scheduler);
|
|
cc.P2.scheduler = null;
|
|
}
|
|
}
|
|
this.CWMF = 0;
|
|
} else if (forTest) {
|
|
this.CWMF = 0;
|
|
if (this === cc.P1) {
|
|
this.access(0x05); // B = [M]: load DD for test
|
|
this.C = this.B % 0x7FFF;
|
|
this.L = 0;
|
|
this.access(0x30); // P = [C]: first word of test routine
|
|
this.G = 0;
|
|
this.H = 0;
|
|
this.K = 0;
|
|
this.V = 0;
|
|
} else {
|
|
this.T = 0; // idle the processor
|
|
this.TROF = 0;
|
|
this.PROF = 0;
|
|
this.busy = false;
|
|
cc.HP2F = 1;
|
|
cc.P2BF = 0;
|
|
if (cc.P2.scheduler) {
|
|
cancelTimeout(cc.P2.scheduler);
|
|
cc.P2.scheduler = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
B5500Processor.initiate = function(forTest) {
|
|
/* Initiates the processor from interrupt control words stored in the
|
|
stack. Assumes the INCW is in A. "forTest" implies use from IFT */
|
|
var saveAROF;
|
|
var saveBROF;
|
|
var temp;
|
|
|
|
// restore the Initiate Control Word or Initiate Test Control Word
|
|
this.S = this.A % 0x8000;
|
|
this.CWMF = Math.floor(this.A / 0x8000) % 0x02;
|
|
if (forTest) {
|
|
this.TM = Math.floor(this.A / 0x10000) % 0x20;
|
|
this.Z = Math.floor(this.A / 0x400000) % 0x40;
|
|
this.Y = Math.floor(this.A / 0x10000000) % 0x40;
|
|
this.Q = Math.floor(this.A / 0x400000000) % 0x200;
|
|
this.TM |= Math.floor(this.A / 0x200000( % 0x02 << 5; // CCCF
|
|
this.TM |= Math.floor(this.A / 0x80000000000) % 0x02 << 6; // MWOF
|
|
this.TM |= Math.floor(this.A / 0x400000000000) % 0x02 << 7; // MROF
|
|
// Emulator doesn't support J register, so can't set that from TM
|
|
}
|
|
this.AROF = 0;
|
|
this.BROF = 0;
|
|
|
|
// restore the Interrupt Return Control Word
|
|
this.access(0x03); // B = [S]
|
|
this.S--;
|
|
this.C = this.B % 0x8000;
|
|
this.F = Math.floor(this.B / 0x8000) % 0x8000;
|
|
this.K = Math.floor(this.B / 0x40000000) % 0x08;
|
|
this.G = Math.floor(this.B / 0x200000000) % 0x08;
|
|
this.L = Math.floor(this.B / 0x1000000000) % 0x04;
|
|
this.V = Math.floor(this.B / 0x4000000000) % 0x08;
|
|
this.H = Math.floor(this.B / 0x20000000000) % 0x08;
|
|
this.access(0x30); // P = [C]
|
|
if (this.CWMF || forTest) {
|
|
saveBROF = Math.floor(this.B / 200000000000) % 0x02;
|
|
}
|
|
|
|
// restore the Interrupt Control Word
|
|
this.access(0x03); // B = [S]
|
|
this.S--;
|
|
this.VARF = Math.floor(this.B / 0x1000000) % 0x02;
|
|
this.SALF = Math.floor(this.B / 0x40000000) % 0x02;
|
|
this.MSFF = Math.floor(this.B / 0x80000000) % 0x02;
|
|
this.R = (Math.floor(this.B / 0x200000000) % 0x200);
|
|
|
|
if (this.CWMF || forTest) {
|
|
this.M = this.B % 0x8000;
|
|
this.N = Math.floor(this.B / 0x8000) % 0x10;
|
|
|
|
// restore the CM Interrupt Loop Control Word
|
|
this.access(0x03); // B = [S]
|
|
this.S--;
|
|
this.X = this.B % 0x8000000000;
|
|
saveAROF = Math.floor(this.B / 0x400000000000) % 0x02;
|
|
|
|
// restore the B register
|
|
if (saveBROF || forTest) {
|
|
this.access(0x03); // B = [S]
|
|
this.S--;
|
|
}
|
|
|
|
// restore the A register
|
|
if (saveAROF || forTest) {
|
|
this.access(0x02); // A = [S]
|
|
this.S--;
|
|
}
|
|
|
|
if (this.CWMF) {
|
|
// exchange S with its field in X
|
|
temp = this.S;
|
|
this.S = (this.X % 0x40000000) >>> 15;
|
|
this.X = this.X % 0x8000 +
|
|
temp * 0x8000 +
|
|
Math.floor(this.X / 0x40000000) * 0x40000000;
|
|
}
|
|
// else don't restore A or B for word mode -- will pop up as necessary
|
|
}
|
|
|
|
this.T = Math.floor(this.P / Math.pow(2, 36-this.L*12)) % 0x1000; // ugly
|
|
this.TROF = 1;
|
|
if (forTest) {
|
|
this.NCSF = (this.TM >>> 4) & 0x01;
|
|
this.CCCF = (this.TM >>> 5) & 0x01;
|
|
this.MWOF = (this.TM >>> 6) & 0x01;
|
|
this.MROF = (this.TM >>> 7) & 0x01;
|
|
this.S--;
|
|
if (!this.CCCF) {
|
|
this.TM |= 0x80;
|
|
}
|
|
} else {
|
|
this.NCSF = 1;
|
|
this.busy = true;
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.run = function() {
|
|
/* Instruction execution driver for the B5500 processor. This function is
|
|
an artifact of the emulator design and does not represent any physical
|
|
process or state of the processor. This routine assumes the registers are
|
|
set up, and in particular a syllable is in T with TROF set. It will run
|
|
until cycleCount >= cycleLimit or !this.busy */
|
|
var opcode;
|
|
var t1;
|
|
var t2;
|
|
var variant;
|
|
|
|
do {
|
|
this.Q = 0;
|
|
this.Y = 0;
|
|
this.Z = 0;
|
|
opcode = this.T;
|
|
if (this.CWMF) {
|
|
/***********************************************************
|
|
* Character Mode Syllables *
|
|
***********************************************************/
|
|
variant = opcode >>> 6;
|
|
switch (opcode & 0x3F) {
|
|
case 0x00: // XX00: CMX, EXC: Exit character mode
|
|
break;
|
|
|
|
case 0x02: // XX02: BSD=Skip bit destination
|
|
break;
|
|
|
|
case 0x03: // XX03: BSS=Skip bit source
|
|
break;
|
|
|
|
case 0x04: // XX04: RDA=Recall destination address
|
|
break;
|
|
|
|
case 0x05: // XX05: TRW=Transfer words
|
|
break;
|
|
|
|
case 0x06: // XX06: SED=Set destination address
|
|
break;
|
|
|
|
case 0x07: // XX07: TDA=Transfer destination address
|
|
break;
|
|
|
|
case 0x0A: // XX12: TBN=Transfer blank for numeric
|
|
break;
|
|
|
|
case 0x0C: // XX14: SDA=Store destination address
|
|
break;
|
|
|
|
case 0x0D: // XX15: SSA=Store source address
|
|
break;
|
|
|
|
case 0x0E: // XX16: SFD=Skip forward destination
|
|
break;
|
|
|
|
case 0x0F: // XX17: SRD=Skip reverse destination
|
|
break;
|
|
|
|
case 0x11: // XX11: control state ops
|
|
switch (variant) {
|
|
case 0x14: // 2411: ZPI=Conditional Halt
|
|
break;
|
|
|
|
case 0x18: // 3011: SFI=Store for Interrupt
|
|
this.storeForInterrupt(false);
|
|
break;
|
|
|
|
case 0x1C: // 3411: SFT=Store for Test
|
|
this.storeForInterrupt(true);
|
|
break;
|
|
|
|
default: // Anything else is a no-op
|
|
break;
|
|
} // end switch for XX11 ops
|
|
break;
|
|
|
|
case 0x12: // XX22: SES=Set source address
|
|
break;
|
|
|
|
case 0x14: // XX24: TEQ=Test for equal
|
|
break;
|
|
|
|
case 0x15: // XX25: TNE=Test for not equal
|
|
break;
|
|
|
|
case 0x16: // XX26: TEG=Test for greater or equal
|
|
break;
|
|
|
|
case 0x17: // XX27: TGR=Test for greater
|
|
break;
|
|
|
|
case 0x18: // XX30: SRS=Skip reverse source
|
|
break;
|
|
|
|
case 0x19: // XX31: SFS=Skip forward source
|
|
break;
|
|
|
|
case 0x1A: // XX32: ---=Field subtract (aux) !! ??
|
|
break;
|
|
|
|
case 0x1B: // XX33: ---=Field add (aux) !! ??
|
|
break;
|
|
|
|
case 0x1C: // XX34: TEL=Test for equal
|
|
break;
|
|
|
|
case 0x1D: // XX35: TLS=Test for less
|
|
break;
|
|
|
|
case 0x1E: // XX36: TAN=Test for alphanumeric
|
|
break;
|
|
|
|
case 0x1F: // XX37: BIT=Test bit
|
|
break;
|
|
|
|
case 0x20: // XX40: INC=Increase TALLY
|
|
if (variant) {
|
|
this.R = (this.R + variant) & 0x3F;
|
|
// else it's a character-mode no-op
|
|
}
|
|
break;
|
|
|
|
case 0x21: // XX41: STC=Store TALLY
|
|
break;
|
|
|
|
case 0x22: // XX42: SEC=Set TALLY
|
|
this.R = variant;
|
|
break;
|
|
|
|
case 0x23: // XX43: CRF=Call variant field
|
|
break;
|
|
|
|
case 0x24: // XX44: JNC=Jump out of loop conditional
|
|
break;
|
|
|
|
case 0x25: // XX45: JFC=Jump forward conditional
|
|
break;
|
|
|
|
case 0x26: // XX46: JNS=Jump out of loop
|
|
break;
|
|
|
|
case 0x27: // XX47: JFW=Jump forward unconditional
|
|
break;
|
|
|
|
case 0x28: // XX50: RCA=Recall control address
|
|
break;
|
|
|
|
case 0x29: // XX51: ENS=End loop
|
|
break;
|
|
|
|
case 0x2A: // XX52: BNS=Begin loop
|
|
break;
|
|
|
|
case 0x2B: // XX53: RSA=Recall source address
|
|
break;
|
|
|
|
case 0x2C: // XX54: SCA=Store control address
|
|
break;
|
|
|
|
case 0x2D: // XX55: JRC=Jump reverse conditional
|
|
break;
|
|
|
|
case 0x2E: // XX56: TSA=Transfer source address
|
|
break;
|
|
|
|
case 0x2F: // XX57: JRV=Jump reverse unconditional
|
|
break;
|
|
|
|
case 0x30: // XX60: CEQ=Compare equal
|
|
break;
|
|
|
|
case 0x31: // XX61: CNE=Compare not equal
|
|
break;
|
|
|
|
case 0x32: // XX62: CEG=Compare greater or equal
|
|
break;
|
|
|
|
case 0x33: // XX63: CGR=Compare greater
|
|
break;
|
|
|
|
case 0x34: // XX64: BIS=Set bit
|
|
break;
|
|
|
|
case 0x35: // XX65: BIR=Reset bit
|
|
break;
|
|
|
|
case 0x36: // XX66: OCV=Output convert
|
|
break;
|
|
|
|
case 0x37: // XX67: ICV=Input convert
|
|
break;
|
|
|
|
case 0x38: // XX70: CEL=Compare equal or less
|
|
break;
|
|
|
|
case 0x39: // XX71: CLS=Compare less
|
|
break;
|
|
|
|
case 0x3A: // XX72: FSU=Field subtract
|
|
break;
|
|
|
|
case 0x3B: // XX73: FAD=Field add
|
|
break;
|
|
|
|
case 0x3C: // XX74: TRP=Transfer program characters
|
|
break;
|
|
|
|
case 0x3D: // XX75: TRN=Transfer numerics
|
|
break;
|
|
|
|
case 0x3E: // XX76: TRZ=Transfer zones
|
|
break;
|
|
|
|
case 0x3F: // XX77: TRS=Transfer source characters
|
|
break;
|
|
|
|
default: // everything else is a no-op
|
|
break;
|
|
} // end switch for character mode operators
|
|
} else {
|
|
/***********************************************************
|
|
* Word Mode Syllables *
|
|
***********************************************************/
|
|
this.M = 0;
|
|
this.N = 0;
|
|
this.X = 0;
|
|
switch (opcode & 3) {
|
|
case 0: // LITC: Literal Call
|
|
this.adjustAEmpty();
|
|
this.A = opcode >>> 2;
|
|
this.AROF = 1;
|
|
break;
|
|
|
|
case 2: // OPDC: Operand Call
|
|
this.adjustAEmpty();
|
|
// TO BE PROVIDED
|
|
break;
|
|
|
|
case 3: // DESC: Descriptor (name) Call
|
|
this.adjustAEmpty();
|
|
// TO BE PROVIDED
|
|
break;
|
|
|
|
case 1: // all other word-mode operators
|
|
variant = opcode >>> 6;
|
|
switch (opcode & 0x3F) {
|
|
case 0x01: // XX01: single-precision numerics
|
|
switch (variant) {
|
|
case 0x01: // 0101: ADD=single-precision add
|
|
break;
|
|
|
|
case 0x03: // 0301: SUB=single-precision subtract
|
|
break;
|
|
|
|
case 0x04: // 0401: MUL=single-precision multiply
|
|
break;
|
|
|
|
case 0x08: // 1001: DIV=single-precision floating divide
|
|
break;
|
|
|
|
case 0x18: // 3001: IDV=integer divide
|
|
break;
|
|
|
|
case 0x38: // 7001: RDV=remainder divide
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x05: // XX05: double-precision numerics
|
|
switch (variant) {
|
|
case 0x01: // 0105: DLA=double-precision add
|
|
break;
|
|
|
|
case 0x03: // 0305: DLS=double-precision subtract
|
|
break;
|
|
|
|
case 0x04: // 0405: DLM=double-precision multiply
|
|
break;
|
|
|
|
case 0x08: // 1005: DLD=double-precision floating divide
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x09: // XX11: control state and communication ops
|
|
switch (variant) {
|
|
case 0x01: // 0111: PRL=Program Release
|
|
break;
|
|
|
|
case 0x10: // 1011: COM=Communicate
|
|
if (this.NCSF) { // no-op in control state
|
|
this.adjustAFull();
|
|
this.M = (this.R << 6) + 0x09; // address = R+@11
|
|
this.access(0x0C); // [M] = A
|
|
this.AROF = 0;
|
|
this.I = (this.I & 0x0F) | 0x40; // set I07
|
|
cc.signalInterrupt();
|
|
}
|
|
break;
|
|
|
|
case 0x02: // 0211: ITI=Interrogate Interrupt
|
|
if (cc.IAR && !this.NCSF) {
|
|
this.C = cc.IAR;
|
|
this.L = 0;
|
|
this.S = 0x40; // address @100
|
|
cc.clearInterrupt();
|
|
cc.access(0x30); // P = [C]
|
|
}
|
|
break;
|
|
|
|
case 0x04: // 0411: RTR=Read Timer
|
|
if (!this.NCSF) { // control-state only
|
|
this.adjustAEmpty();
|
|
this.A = cc.CCI03F << 6 | cc.TM;
|
|
}
|
|
break;
|
|
|
|
case 0x11: // 2111: IOR=I/O Release
|
|
break;
|
|
|
|
case 0x12: // 2211: HP2=Halt Processor 2
|
|
if (!this.NCSF & cc.P2 && cc.P2BF) {
|
|
cc.HP2F = 1;
|
|
// We know P2 is not currently running on this thread, so save its registers
|
|
cc.P2.storeForInterrupt(false);
|
|
}
|
|
break;
|
|
|
|
case 0x14: // 2411: ZPI=Conditional Halt
|
|
break;
|
|
|
|
case 0x18: // 3011: SFI=Store for Interrupt
|
|
this.storeForInterrupt(false);
|
|
break;
|
|
|
|
case 0x1C: // 3411: SFT=Store for Test
|
|
this.storeForInterrupt(true);
|
|
break;
|
|
|
|
case 0x21: // 4111: IP1=Initiate Processor 1
|
|
if (!this.NCSF) {
|
|
this.initiate(false);
|
|
}
|
|
break;
|
|
|
|
case 0x22: // 4211: IP2=Initiate Processor 2
|
|
if (!this.NCSF) {
|
|
this.adjustAFull();
|
|
this.M = 8; // INCW is stored in @10
|
|
this.access(0x0C); // [M] = A
|
|
this.AROF = 0;
|
|
cc.initiateP2();
|
|
this.cycleLimit = 0; // give P2 a chance to run
|
|
}
|
|
break;
|
|
|
|
case 0x24: // 4411: IIO=Initiate I/O
|
|
break;
|
|
|
|
case 0x29: // 5111: IFT=Initiate For Test
|
|
break;
|
|
} // end switch for XX11 ops
|
|
break;
|
|
|
|
case 0x0D: // XX15: logical (bitmask) ops
|
|
switch (variant) {
|
|
case 0x01: // 0115: LNG=logical negate
|
|
break;
|
|
|
|
case 0x02: // 0215: LOR=logical OR
|
|
break;
|
|
|
|
case 0x04: // 0415: LND=logical AND
|
|
break;
|
|
|
|
case 0x08: // 1015: LQV=logical EQV
|
|
break;
|
|
|
|
case 0x10: // 2015: MOP=reset flag bit (make operand)
|
|
break;
|
|
|
|
case 0x20: // 4015: MDS=set flag bit (make descriptor)
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x11: // XX21: load & store ops
|
|
switch (variant) {
|
|
case 0x01: // 0121: CID=Conditional integer store descructive
|
|
break;
|
|
|
|
case 0x02: // 0221: CIN=Conditional integer store nondestructive
|
|
break;
|
|
|
|
case 0x04: // 0421: STD=Store destructive
|
|
break;
|
|
|
|
case 0x08: // 1021: SND=Store nondestructive
|
|
break;
|
|
|
|
case 0x10: // 2021: LOD=Load operand
|
|
break;
|
|
|
|
case 0x21: // 4121: ISD=Integer store destructive
|
|
break;
|
|
|
|
case 0x22: // 4221: ISN=Integer store nondestructive
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x15: // XX25: comparison & misc. stack ops
|
|
switch (variant) {
|
|
case 0x01: // 0125: CEQ=compare B greater or equal to A
|
|
break;
|
|
|
|
case 0x02: // 0225: CGR=compare B greater to A
|
|
break;
|
|
|
|
case 0x04: // 0425: NEQ=compare B not equal to A
|
|
break;
|
|
|
|
case 0x08: // 1025: XCH=exchange B with A
|
|
this.adjustAFull();
|
|
this.adjustBFull();
|
|
t1 = this.A;
|
|
this.A = this.B;
|
|
this.B = t1;
|
|
break;
|
|
|
|
case 0x0C: // 1425: FTC=F field to core field
|
|
break;
|
|
|
|
case 0x10: // 2025: DUP=Duplicate TOS
|
|
this.adjustAEmpty();
|
|
this.adjustBFull();
|
|
this.A = this.B;
|
|
this.AROF = 1;
|
|
break;
|
|
|
|
case 0x1C: // 3425: FTF=F field to F field
|
|
break;
|
|
|
|
case 0x21: // 4125: LEQ=compare B less or equal to A
|
|
break;
|
|
|
|
case 0x22: // 4225: LSS=compare B less to A
|
|
break;
|
|
|
|
case 0x24: // 4425: EQL=compare B equal to A
|
|
break;
|
|
|
|
case 0x2C: // 5425: CTC=core field to C field
|
|
break;
|
|
|
|
case 0x3C: // 7425: CTF=cre field to F field
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x19: // XX31: branch, sign-bit, interrogate ops
|
|
switch (variant) {
|
|
case 0x01: // 0131: BBC=branch backward conditional
|
|
break;
|
|
|
|
case 0x02: // 0231: BFC=branch forward conditional
|
|
break;
|
|
|
|
case 0x04: // 0431: SSN=set sign bit (set negative)
|
|
break;
|
|
|
|
case 0x08: // 1031: CHS=change sign bit
|
|
break;
|
|
|
|
case 0x10: // 2031: TOP=test flag bit (test for operand)
|
|
break;
|
|
|
|
case 0x11: // 2131: LBC=branch backward word conditional
|
|
break;
|
|
|
|
case 0x12: // 2231: LFC=branch forward word conditional
|
|
break;
|
|
|
|
case 0x14: // 2431: TUS=interrogate peripheral status
|
|
break;
|
|
|
|
case 0x21: // 4131: BBW=branch backward unconditional
|
|
break;
|
|
|
|
case 0x22: // 4231: BFW=branch forward unconditional
|
|
break;
|
|
|
|
case 0x24: // 4431: SSP=reset sign bit (set positive)
|
|
break;
|
|
|
|
case 0x31: // 6131: LBU=branch backward word unconditional
|
|
break;
|
|
|
|
case 0x32: // 6231: LFU=branch forward word unconditional
|
|
break;
|
|
|
|
case 0x34: // 6431: TIO=interrogate I/O channel
|
|
break;
|
|
|
|
case 0x38: // 7031: FBS=stack search for flag
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x1D: // XX35: exit & return ops
|
|
switch (variant) {
|
|
case 0x01: // 0135: BRT=branch return
|
|
break;
|
|
|
|
case 0x02: // 0235: RTN=return normal
|
|
break;
|
|
|
|
case 0x04: // 0435: XIT=exit procedure
|
|
break;
|
|
|
|
case 0x0A: // 1235: RTS=return special
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x21: // XX41: index, mark stack, etc.
|
|
switch (variant) {
|
|
case 0x01: // 0141: INX=index
|
|
break;
|
|
|
|
case 0x02: // 0241: COC=construct operand call
|
|
break;
|
|
|
|
case 0x04: // 0441: MKS=mark stack
|
|
break;
|
|
|
|
case 0x0A: // 1241: CDC=construct descriptor call
|
|
break;
|
|
|
|
case 0x11: // 2141: SSF=F & S register set/store
|
|
break;
|
|
|
|
case 0x15: // 2541: LLL=link list lookup
|
|
break;
|
|
|
|
case 0x24: // 4441: CMN=enter character mode inline
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x25: // XX45: ISO=Variable Field Isolate op
|
|
break;
|
|
|
|
case 0x29: // XX51: delete & conditional branch ops
|
|
if (variant == 0) { // 0051=DEL: delete TOS
|
|
if (this.AROF) {
|
|
this.AROF = 0;
|
|
} else if (this.BROF) {
|
|
this.BROF = 0;
|
|
} else {
|
|
this.S--;
|
|
}
|
|
} else {
|
|
switch (variant & 0x03) {
|
|
case 0x00: // X051/X451: CFN=non-zero field branch forward nondestructive
|
|
break;
|
|
|
|
case 0x01: // X151/X551: CBN=non-zero field branch backward nondestructive
|
|
break;
|
|
|
|
case 0x02: // X251/X651: CFD=non-zero field branch forward destructive
|
|
break;
|
|
|
|
case 0x03: // X351/X751: CBD=non-zero field branch backward destructive
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x2D: // XX55: NOP & DIA=Dial A ops
|
|
if (opcode & 0xFC0) {
|
|
this.G = variant >>> 3;
|
|
this.H = (variant) & 7;
|
|
// else // 0055: NOP=no operation (the official one, at least)
|
|
}
|
|
break;
|
|
|
|
case 0x31: // XX61: XRT & DIB=Dial B ops
|
|
if (opcode & 0xFC0) {
|
|
this.K = variant >>> 3;
|
|
this.V = (variant) & 7;
|
|
} else { // 0061=XRT: temporarily set full PRT addressing mode
|
|
this.VARF = this.SALF;
|
|
this.SALF = 0;
|
|
}
|
|
break;
|
|
|
|
case 0x35: // XX65: TRB=Transfer Bits
|
|
this.adjustAFull();
|
|
this.adjustBFull();
|
|
t1 = this.G <<< 3 | this.H; // A register starting bit nr
|
|
if (t1+variant > 48) {
|
|
variant = 48-t1;
|
|
}
|
|
t2 = this.K <<< 3 | this.V; // B register starting bit nr
|
|
if (t2+variant > 48) {
|
|
variant = 48-t2;
|
|
}
|
|
if (variant > 0) {
|
|
this.B = cc.insert(this.B, t2, variant, cc.isolate(this.A, t1, variant));
|
|
}
|
|
this.AROF = 0;
|
|
this.cycleCount += variant + this.G + this.K; // approximate the shift counts
|
|
break;
|
|
|
|
case 0x39: // XX71: FCL=Compare Field Low
|
|
this.adjustAFull();
|
|
this.adjustBFull();
|
|
t1 = this.G <<< 3 | this.H; // A register starting bit nr
|
|
if (t1+variant > 48) {
|
|
variant = 48-t1;
|
|
}
|
|
t2 = this.K <<< 3 | this.V; // B register starting bit nr
|
|
if (t2+variant > 48) {
|
|
variant = 48-t2;
|
|
}
|
|
if (variant > 0 && cc.isolate(this.B, t2, variant) < cc.isolate(this.A, t1, variant)) {
|
|
this.A = 1;
|
|
} else {
|
|
this.A = 0;
|
|
}
|
|
this.cycleCount += variant + this.G + this.K; // approximate the shift counts
|
|
break;
|
|
|
|
case 0x3D: // XX75: FCE=Compare Field Equal
|
|
this.adjustAFull();
|
|
this.adjustBFull();
|
|
t1 = this.G <<< 3 | this.H; // A register starting bit nr
|
|
if (t1+variant > 48) {
|
|
variant = 48-t1;
|
|
}
|
|
t2 = this.K <<< 3 | this.V; // B register starting bit nr
|
|
if (t2+variant > 48) {
|
|
variant = 48-t2;
|
|
}
|
|
if (variant > 0 && cc.isolate(this.B, t2, variant) == cc.isolate(this.A, t1, variant)) {
|
|
this.A = 1;
|
|
} else {
|
|
this.A = 0;
|
|
}
|
|
this.cycleCount += variant + this.G + this.K; // approximate the shift counts
|
|
break;
|
|
|
|
default:
|
|
break; // anything else is a no-op
|
|
} // end switch for non-LITC/OPDC/DESC operators
|
|
break;
|
|
} // end switch for word-mode operators
|
|
} // end main switch for opcode dispatch
|
|
|
|
/***************************************************************
|
|
* SECL: Syllable Execution Complete Level *
|
|
***************************************************************/
|
|
if ((this === cc.P1 ? cc.IAR : this.I) && this.NCSF) {
|
|
// there's an interrupt and we're in normal state
|
|
this.T = 0x0609; // inject 3011=SFI into T
|
|
this.Q |= 0x40 // set Q07F to indicate hardware-induced SFI
|
|
this.Q &= ~(0x100); // reset Q09F: adder mode for R-relative addressing
|
|
} else {
|
|
// otherwise, fetch the next instruction
|
|
switch (this.L) {
|
|
case 0:
|
|
this.T = Math.Floor(this.P / 0x1000000000) % 0x1000;
|
|
this.L++;
|
|
case 1:
|
|
this.T = Math.Floor(this.P / 0x1000000) % 0x1000;
|
|
this.L++;
|
|
case 2:
|
|
this.T = Math.Floor(this.P / 0x1000) % 0x1000;
|
|
this.L++;
|
|
case 3:
|
|
this.T = this.P % 0x1000;
|
|
this.L = 0;
|
|
this.C++;
|
|
this.access(0x30); // P = [C]
|
|
}
|
|
}
|
|
} while ((this.cycleCount += 2) < this.cycleLimit && this.busy);
|
|
}
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.schedule = function schedule() {
|
|
/* Schedules the processor run time and attempts to throttle performance
|
|
to approximate that of a real B5500. Well, at least we hope this will run
|
|
fast enough that the performance will need to be throttled. It establishes
|
|
a timeslice in terms of a number of processor "cycles" of 1 microsecond
|
|
each and calls run() to execute at most that number of cycles. run()
|
|
counts up cycles until it reaches this limit or some terminating event
|
|
(such as a halt), then exits back here. If the processor remains active,
|
|
this routine will reschedule itself for an appropriate later time, thereby
|
|
throttling the performance and allowing other modules a chance at the
|
|
Javascript execution thread. */
|
|
var delayTime;
|
|
var that = schedule.that;
|
|
|
|
that.scheduler = null;
|
|
that.cycleLimit = that.timeSlice;
|
|
that.cycleCount = 0;
|
|
|
|
that.run();
|
|
|
|
that.totalCycles += that.cycleCount
|
|
that.procTime += that.cycleCount;
|
|
if (that.busy) {
|
|
delayTime = that.procTime/1000 - new Date().getTime();
|
|
that.scheduleSlack += delayTime;
|
|
that.scheduler = setTimeout(that.schedule, (delayTime < 0 ? 0 : delayTime));
|
|
}
|
|
} |