mirror of
https://github.com/pkimpel/retro-b5500.git
synced 2026-02-12 19:27:39 +00:00
2. Fix normalization and rounding problems in Processor. 3. Fix Card-Load-Select -- emulator now boots from cards. 4. Commit LDCNTRL label and end cards to source CONTROL.DECK.job 5. Delete unused activeIOUnit property in all I/O drivers. 6. Change CardReader to reset file selector control when hopper becomes empty, instead of when START button is clicked. 7. Minor adjustments to CentralControl annunciator layout and behavior on Console. 8. Increase SPO paper scroll-back to 1500 lines (was 500). 9. Correct Card-Load-Select behavior in SyllableDebugger. 10. Implement setCallback() to replace setImmediate() in SyllableDebugger.
4642 lines
209 KiB
JavaScript
4642 lines
209 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
|
|
************************************************************************
|
|
* B5500 Processor (CPU) module.
|
|
*
|
|
* Instance variables in all caps generally refer to register or flip-flop (FF)
|
|
* entities in the processor hardware. See the Burroughs B5500 Reference Manual
|
|
* (1021326, May 1967) and B5281 Processor Training Manual (B5281.55, August 1966)
|
|
* for details:
|
|
* http://bitsavers.org/pdf/burroughs/B5000_5500_5700/1021326_B5500_RefMan_May67.pdf
|
|
* http://bitsavers.org/pdf/burroughs/B5000_5500_5700/B5281.55_ProcessorTrainingManual_Aug66.pdf
|
|
*
|
|
* B5500 word format: 48 bits plus (hidden) parity.
|
|
* Bit 0 is high-order, bit 47 is low-order, big-endian character ordering.
|
|
* [0:1] Flag bit (1=control word or descriptor)
|
|
* [1:1] Mantissa sign bit (1=negative)
|
|
* [2:1] Exponent sign bit (1=negative)
|
|
* [3:6] Exponent (power of 8, signed-magnitude)
|
|
* [9:39] Mantissa (signed-magnitude, scaling point after bit 47)
|
|
*
|
|
************************************************************************
|
|
* 2012-06-03 P.Kimpel
|
|
* Original version, from thin air.
|
|
***********************************************************************/
|
|
"use strict";
|
|
|
|
/**************************************/
|
|
function B5500Processor(procID, cc) {
|
|
/* Constructor for the Processor module object */
|
|
|
|
this.processorID = procID; // Processor ID ("A" or "B")
|
|
this.cc = cc; // Reference back to Central Control module
|
|
this.scheduler = null; // Reference to current setCallback token
|
|
this.accessor = { // Memory access control block
|
|
requestorID: procID, // Memory requestor ID
|
|
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.clear(); // Create and initialize the processor state
|
|
|
|
this.delayDeltaAvg = 0; // Average difference between requested and actual setCallback() delays, ms
|
|
this.delayLastStamp = 0; // Timestamp of last setCallback() delay, ms
|
|
this.delayRequested = 0; // Last requested setCallback() delay, ms
|
|
}
|
|
|
|
/**************************************/
|
|
|
|
B5500Processor.cyclesPerMilli = 1000; // clock cycles per millisecond (1000 = 1.0 MHz)
|
|
B5500Processor.timeSlice = 4000; // this.run() timeslice, clocks
|
|
B5500Processor.delaySamples = 1000; // this.delayDeltaAvg sampling average basis
|
|
|
|
B5500Processor.collation = [ // index by BIC to get collation value
|
|
53, 54, 55, 56, 57, 58, 59, 60, // @00: 0 1 2 3 4 5 6 7
|
|
61, 62, 19, 20, 63, 21, 22, 23, // @10: 8 9 # @ ? : > }
|
|
24, 25, 26, 27, 28, 29, 30, 31, // @20: + A B C D E F G
|
|
32, 33, 1, 2, 6, 3, 4, 5, // @30: H I . [ & ( < ~
|
|
34, 35, 36, 37, 38, 39, 40, 41, // @40: | J K L M N O P
|
|
42, 43, 7, 8, 12, 9, 10, 11, // @50: Q R $ * - ) ; {
|
|
0, 13, 45, 46, 47, 48, 49, 50, // @60: _ / S T U V W X (_ = blank)
|
|
51, 52, 14, 15, 44, 16, 17, 18]; // @70: Y Z , % ! = ] "
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.clear = function clear() {
|
|
/* Initializes (and if necessary, creates) the processor state */
|
|
|
|
this.A = 0; // Top-of-stack register A
|
|
this.AROF = 0; // A Register Occupied FF
|
|
this.B = 0; // Top-of-stack register B
|
|
this.BROF = 0; // B Register Occupied FF
|
|
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; // E-register Inhibit Address FF
|
|
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.US14X = 0; // STOP OPERATOR switch
|
|
|
|
this.isP1 = (this === this.cc.P1); // True if this is the control processor
|
|
this.busy = 0; // Processor is running, not idle or halted
|
|
this.controlCycles = 0; // Current control-state cycle count (for UI display)
|
|
this.cycleCount = 0; // Cycle count for current syllable
|
|
this.cycleLimit = 0; // Cycle limit for this.run()
|
|
this.normalCycles = 0; // Current normal-state cycle count (for UI display)
|
|
this.runCycles = 0; // Current cycle cound for this.run()
|
|
this.totalCycles = 0; // Total cycles executed on this processor
|
|
this.procStart = 0; // Javascript time that the processor started running, ms
|
|
this.procTime = 0.001; // Total processor running time, ms
|
|
this.procSlack = 0; // Total processor throttling delay, ms
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.accessError = function accessError() {
|
|
/* Common error handling routine for all memory acccesses */
|
|
|
|
if (this.accessor.MAED) {
|
|
this.I |= 0x02; // set I02F: memory address/inhibit error
|
|
this.cc.signalInterrupt();
|
|
} else if (this.accessor.MPED) {
|
|
this.I |= 0x01; // set I01F: memory parity error
|
|
this.cc.signalInterrupt();
|
|
if (this.isP1 && !this.NCSF) {
|
|
this.stop(); // P1 memory parity in control state stops the proc
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.loadAviaS = function loadAviaS() {
|
|
/* Load the A register from the address in S */
|
|
var acc = this.accessor; // get a local reference to the accessor object
|
|
|
|
this.E = 0x02; // Just to show the world what's happening
|
|
acc.addr = this.S;
|
|
acc.MAIL = (this.S < 0x0200 && this.NCSF);
|
|
this.cc.fetch(acc);
|
|
this.cycleCount += B5500CentralControl.memReadCycles;
|
|
if (acc.MAED || acc.MPED) {
|
|
this.accessError();
|
|
} else {
|
|
this.A = acc.word;
|
|
this.AROF = 1;
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.loadBviaS = function loadBviaS() {
|
|
/* Load the B register from the address in S */
|
|
var acc = this.accessor; // get a local reference to the accessor object
|
|
|
|
this.E = 0x03; // Just to show the world what's happening
|
|
acc.addr = this.S;
|
|
acc.MAIL = (this.S < 0x0200 && this.NCSF);
|
|
this.cc.fetch(acc);
|
|
this.cycleCount += B5500CentralControl.memReadCycles;
|
|
if (acc.MAED || acc.MPED) {
|
|
this.accessError();
|
|
} else {
|
|
this.B = acc.word;
|
|
this.BROF = 1;
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.loadAviaM = function loadAviaM() {
|
|
/* Load the A register from the address in M */
|
|
var acc = this.accessor; // get a local reference to the accessor object
|
|
|
|
this.E = 0x04; // Just to show the world what's happening
|
|
acc.addr = this.M;
|
|
acc.MAIL = (this.M < 0x0200 && this.NCSF);
|
|
this.cc.fetch(acc);
|
|
this.cycleCount += B5500CentralControl.memReadCycles;
|
|
if (acc.MAED || acc.MPED) {
|
|
this.accessError();
|
|
} else {
|
|
this.A = acc.word;
|
|
this.AROF = 1;
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.loadBviaM = function loadBviaM() {
|
|
/* Load the B register from the address in M */
|
|
var acc = this.accessor; // get a local reference to the accessor object
|
|
|
|
this.E = 0x05; // Just to show the world what's happening
|
|
acc.addr = this.M;
|
|
acc.MAIL = (this.M < 0x0200 && this.NCSF);
|
|
this.cc.fetch(acc);
|
|
this.cycleCount += B5500CentralControl.memReadCycles;
|
|
if (acc.MAED || acc.MPED) {
|
|
this.accessError();
|
|
} else {
|
|
this.B = acc.word;
|
|
this.BROF = 1;
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.loadMviaM = function loadMviaM() {
|
|
/* Load the M register from bits [18:15] of the word addressed by M */
|
|
var acc = this.accessor; // get a local reference to the accessor object
|
|
|
|
this.E = 0x06; // Just to show the world what's happening
|
|
acc.addr = this.M;
|
|
acc.MAIL = (this.M < 0x0200 && this.NCSF);
|
|
this.cc.fetch(acc);
|
|
this.cycleCount += B5500CentralControl.memReadCycles;
|
|
if (acc.MAED || acc.MPED) {
|
|
this.accessError();
|
|
} else {
|
|
this.M = (acc.word % 0x40000000) >>> 15;
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.loadPviaC = function loadPviaC() {
|
|
/* Load the P register from the address in C */
|
|
var acc = this.accessor; // get a local reference to the accessor object
|
|
|
|
this.E = 0x30; // Just to show the world what's happening
|
|
acc.addr = this.C;
|
|
acc.MAIL = (this.C < 0x0200 && this.NCSF);
|
|
this.cc.fetch(acc);
|
|
this.PROF = 1; // PROF gets set even for invalid address
|
|
this.cycleCount += B5500CentralControl.memReadCycles;
|
|
if (acc.MAED || acc.MPED) {
|
|
this.accessError();
|
|
} else {
|
|
this.P = acc.word;
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.storeAviaS = function storeAviaS() {
|
|
/* Store the A register at the address in S */
|
|
var acc = this.accessor; // get a local reference to the accessor object
|
|
|
|
this.E = 0x0A; // Just to show the world what's happening
|
|
acc.addr = this.S;
|
|
acc.MAIL = (this.S < 0x0200 && this.NCSF);
|
|
acc.word = this.A;
|
|
this.cc.store(acc);
|
|
this.cycleCount += B5500CentralControl.memWriteCycles;
|
|
if (acc.MAED || acc.MPED) {
|
|
this.accessError();
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.storeBviaS = function storeBviaS() {
|
|
/* Store the B register at the address in S */
|
|
var acc = this.accessor; // get a local reference to the accessor object
|
|
|
|
this.E = 0x0B; // Just to show the world what's happening
|
|
acc.addr = this.S;
|
|
acc.MAIL = (this.S < 0x0200 && this.NCSF);
|
|
acc.word = this.B;
|
|
this.cc.store(acc);
|
|
this.cycleCount += B5500CentralControl.memWriteCycles;
|
|
if (acc.MAED || acc.MPED) {
|
|
this.accessError();
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.storeAviaM = function storeAviaM() {
|
|
/* Store the A register at the address in M */
|
|
var acc = this.accessor; // get a local reference to the accessor object
|
|
|
|
this.E = 0x0C; // Just to show the world what's happening
|
|
acc.addr = this.M;
|
|
acc.MAIL = (this.M < 0x0200 && this.NCSF);
|
|
acc.word = this.A;
|
|
this.cc.store(acc);
|
|
this.cycleCount += B5500CentralControl.memWriteCycles;
|
|
if (acc.MAED || acc.MPED) {
|
|
this.accessError();
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.storeBviaM = function storeBviaM() {
|
|
/* Store the B register at the address in M */
|
|
var acc = this.accessor; // get a local reference to the accessor object
|
|
|
|
this.E = 0x0D; // Just to show the world what's happening
|
|
acc.addr = this.M;
|
|
acc.MAIL = (this.M < 0x0200 && this.NCSF);
|
|
acc.word = this.B;
|
|
this.cc.store(acc);
|
|
this.cycleCount += B5500CentralControl.memWriteCycles;
|
|
if (acc.MAED || acc.MPED) {
|
|
this.accessError();
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.adjustAEmpty = function adjustAEmpty() {
|
|
/* 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
|
|
this.cc.signalInterrupt();
|
|
} else {
|
|
this.S++;
|
|
this.storeBviaS(); // [S] = B
|
|
}
|
|
}
|
|
this.B = this.A;
|
|
this.AROF = 0;
|
|
this.BROF = 1;
|
|
// else we're done -- A is already empty
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.adjustAFull = function adjustAFull() {
|
|
/* 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.loadAviaS(); // A = [S]
|
|
this.S--;
|
|
}
|
|
// else we're done -- A is already full
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.adjustBEmpty = function adjustBEmpty() {
|
|
/* 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
|
|
this.cc.signalInterrupt();
|
|
} else {
|
|
this.S++;
|
|
this.storeBviaS(); // [S] = B
|
|
this.BROF = 0;
|
|
}
|
|
// else we're done -- B is already empty
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.adjustBFull = function adjustBFull() {
|
|
/* Adjusts the B register so that it is full, popping the contents of
|
|
[S] into B, as necessary. */
|
|
|
|
if (!this.BROF) {
|
|
this.loadBviaS(); // B = [S]
|
|
this.S--;
|
|
// else we're done -- B is already full
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.adjustABEmpty = function adjustABEmpty() {
|
|
/* Adjusts the A and B registers so that both are empty, pushing the
|
|
prior contents into memory, as necessary. */
|
|
|
|
if (this.BROF) {
|
|
this.BROF = 0;
|
|
if ((this.S >>> 6) == this.R && this.NCSF) {
|
|
this.I |= 0x04; // set I03F: stack overflow
|
|
this.cc.signalInterrupt();
|
|
} else {
|
|
this.S++;
|
|
this.storeBviaS(); // [S] = B
|
|
}
|
|
}
|
|
if (this.AROF) {
|
|
this.AROF = 0;
|
|
if ((this.S >>> 6) == this.R && this.NCSF) {
|
|
this.I |= 0x04; // set I03F: stack overflow
|
|
this.cc.signalInterrupt();
|
|
} else {
|
|
this.S++;
|
|
this.storeAviaS(); // [S] = A
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.adjustABFull = function adjustABFull() {
|
|
/* Ensures both TOS registers are occupied, pushing up from memory as required */
|
|
|
|
if (this.AROF) {
|
|
if (this.BROF) {
|
|
// A and B are already full, so we're done
|
|
} else {
|
|
// A is full and B is empty, so load B from [S]
|
|
this.loadBviaS(); // B = [S]
|
|
this.S--;
|
|
}
|
|
} else {
|
|
if (this.BROF) {
|
|
// A is empty and B is full, so copy B to A and load B from [S]
|
|
this.A = this.B;
|
|
this.AROF = 1;
|
|
} else {
|
|
// A and B are empty, so simply load them from [S]
|
|
this.loadAviaS(); // A = [S]
|
|
this.S--;
|
|
}
|
|
this.loadBviaS(); // B = [S]
|
|
this.S--;
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.exchangeTOS = function exchangeTOS() {
|
|
/* Exchanges the two top-of-stack values */
|
|
var temp;
|
|
|
|
if (this.AROF) {
|
|
if (this.BROF) {
|
|
// A and B are full, so simply exchange them
|
|
temp = this.A;
|
|
this.A = this.B;
|
|
this.B = temp;
|
|
} else {
|
|
// A is full and B is empty, so push A to B and load A from [S]
|
|
this.B = this.A;
|
|
this.BROF = 1;
|
|
this.loadAviaS(); // A = [S]
|
|
this.S--;
|
|
}
|
|
} else {
|
|
if (this.BROF) {
|
|
// A is empty and B is full, so load A from [S]
|
|
this.loadAviaS(); // A = [S]
|
|
this.S--;
|
|
} else {
|
|
// A and B are empty, so simply load them in reverse order
|
|
this.loadBviaS(); // B = [S]
|
|
this.S--;
|
|
this.loadAviaS(); // A = [S]
|
|
this.S--;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.jumpSyllables = function jumpSyllables(count) {
|
|
/* Adjusts the C and L registers by "count" syllables (which may be negative).
|
|
Forces a fetch to reload the P register after C and L are adjusted.
|
|
On entry, C and L are assumed to be pointing to the next instruction
|
|
to be executed, not the current one */
|
|
var addr;
|
|
|
|
addr = this.C*4 + this.L + count;
|
|
this.C = addr >>> 2;
|
|
this.L = addr & 0x03;
|
|
this.PROF = 0; // require fetch at SECL
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.jumpWords = function jumpWords(count) {
|
|
/* Adjusts the C register by "count" words (which may be negative). L is set
|
|
to zero. Forces a fetch to reload the P register after C and L are adjusted.
|
|
On entry, C is assumed to be pointing to the CURRENT instruction word, i.e.,
|
|
Inhibit Fetch and Inhibit Count for Fetch have both been asserted. Any adjustment
|
|
to C to account for the emulator's automatic C/L increment at SECL is the
|
|
responsibility of the caller */
|
|
|
|
this.C += count;
|
|
this.L = 0;
|
|
this.PROF = 0; // require fetch at SECL
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.jumpOutOfLoop = function jumpOutOfLoop(count) {
|
|
/* Terminates the current character-mode loop by restoring the prior LCW
|
|
(or RCW) from the stack to X. If "count" is not zero, adjusts C & L forward
|
|
by that number of syllables and reloads P to branch to the jump-out location,
|
|
otherwise continues in sequence. Uses A to restore X and invalidates A */
|
|
var t1 = this.S; // save S (not the way the hardware did it)
|
|
|
|
this.cycleCount += 2;
|
|
this.S = this.cc.fieldIsolate(this.X, 18, 15); // get prior LCW addr from X value
|
|
this.loadAviaS(); // A = [S], fetch prior LCW from stack
|
|
if (count) {
|
|
this.cycleCount += (count >>> 2) + (count & 0x03);
|
|
this.jumpSyllables(count);
|
|
}
|
|
this.X = this.A % 0x8000000000; // store prior LCW (39 bits: less control bits) in X
|
|
this.S = t1; // restore S
|
|
this.AROF = 0; // invalidate A
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.streamAdjustSourceChar = function streamAdjustSourceChar() {
|
|
/* Adjusts the character-mode source pointer to the next character
|
|
boundary, as necessary. If the adjustment crosses a word boundary,
|
|
AROF is reset to force reloading later at the new source address */
|
|
|
|
if (this.H > 0) {
|
|
this.H = 0;
|
|
if (this.G < 7) {
|
|
this.G++;
|
|
} else {
|
|
this.G = 0;
|
|
this.AROF = 0;
|
|
this.M++;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.streamAdjustDestChar = function streamAdjustDestChar() {
|
|
/* Adjusts the character-mode destination pointer to the next character
|
|
boundary, as necessary. If the adjustment crosses a word boundary and
|
|
BROF is set, B is stored at S before S is incremented and BROF is reset
|
|
to force reloading later at the new destination address */
|
|
|
|
if (this.V > 0) {
|
|
this.V = 0;
|
|
if (this.K < 7) {
|
|
this.K++;
|
|
} else {
|
|
this.K = 0;
|
|
if (this.BROF) {
|
|
this.storeBviaS(); // [S] = B
|
|
this.BROF = 0;
|
|
}
|
|
this.S++;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.compareSourceWithDest = function compareSourceWithDest(count) {
|
|
/* Compares source characters to destination characters according to the
|
|
processor collating sequence.
|
|
"count" is the number of source characters to process.
|
|
The result of the comparison is left in two flip-flops:
|
|
Q03F=1: an inequality was detected
|
|
MSFF=1: the inequality was source > destination
|
|
If the two strings are equal, Q03F and MSFF will both be zero. Once an
|
|
inequality is encountered, Q03F will be set to 1 and MSFF (also known as
|
|
TFFF) will be set based on the nature of inequality. After this point, the
|
|
processor merely advances its address pointers to exhaust the count and does
|
|
not fetch additional words from memory. Note that the processor uses Q04F to
|
|
inhibit storing the B register at the end of a word boundary. This store
|
|
may be required only for the first word in the destination string, if B may
|
|
have been left in an updated state by a prior syllable */
|
|
var aBit; // A register bit nr
|
|
var bBit; // B register bit nr
|
|
var yc = 0; // local Y register
|
|
var zc = 0; // local Z register
|
|
|
|
this.MSFF = 0;
|
|
this.streamAdjustSourceChar();
|
|
this.streamAdjustDestChar();
|
|
if (count) {
|
|
if (this.BROF) {
|
|
if (this.K == 0) {
|
|
this.Q |= 0x08; // set Q04F -- at start of word, no need to store B later
|
|
}
|
|
} else {
|
|
this.loadBviaS(); // B = [S]
|
|
this.Q |= 0x08; // set Q04F -- just loaded B, no need to store it later
|
|
}
|
|
if (!this.AROF) {
|
|
this.loadAviaM(); // A = [M]
|
|
}
|
|
|
|
// setting Q06F and saving the count in H & V is only significant if this
|
|
// routine is executed as part of Field Add (FAD) or Field Subtract (FSU).
|
|
this.Q |= 0x20; // set Q06F
|
|
this.H = count >>> 3;
|
|
this.V = count & 0x07;
|
|
|
|
aBit = this.G*6; // A-bit number
|
|
bBit = this.K*6; // B-bit number
|
|
do {
|
|
this.cycleCount++; // approximate the timing
|
|
if (this.Q & 0x04) { // inequality already detected -- just count down
|
|
if (count >= 8) {
|
|
count -= 8;
|
|
if (!(this.Q & 0x08)) { // test Q04F to see if B may be dirty
|
|
this.storeBviaS(); // [S] = B
|
|
this.Q |= 0x08; // set Q04F so we won't store B anymore
|
|
}
|
|
this.BROF = 0;
|
|
this.S++;
|
|
this.AROF = 0;
|
|
this.M++;
|
|
} else {
|
|
count--;
|
|
if (this.K < 7) {
|
|
this.K++;
|
|
} else {
|
|
if (!(this.Q & 0x08)) { // test Q04F to see if B may be dirty
|
|
this.storeBviaS(); // [S] = B
|
|
this.Q |= 0x08; // set Q04F so we won't store B anymore
|
|
}
|
|
this.K = 0;
|
|
this.BROF = 0;
|
|
this.S++;
|
|
}
|
|
if (this.G < 7) {
|
|
this.G++;
|
|
} else {
|
|
this.G = 0;
|
|
this.AROF = 0;
|
|
this.M++;
|
|
}
|
|
}
|
|
} else { // strings still equal -- check this character
|
|
if ((yc = this.cc.fieldIsolate(this.A, aBit, 6)) != (zc = this.cc.fieldIsolate(this.B, bBit, 6))) {
|
|
this.Q |= 0x04; // set Q03F to stop further comparison
|
|
this.MSFF = (B5500Processor.collation[yc] > B5500Processor.collation[zc] ? 1 : 0);
|
|
} else { // strings still equal -- advance to next character
|
|
count--;
|
|
if (bBit < 42) {
|
|
bBit += 6;
|
|
this.K++;
|
|
} else {
|
|
bBit = 0;
|
|
this.K = 0;
|
|
if (!(this.Q & 0x08)) { // test Q04F to see if B may be dirty
|
|
this.storeBviaS(); // [S] = B
|
|
this.Q |= 0x08; // set Q04F so we won't store B anymore
|
|
}
|
|
this.S++;
|
|
if (count > 0) {
|
|
this.loadBviaS(); // B = [S]
|
|
} else {
|
|
this.BROF = 0;
|
|
}
|
|
}
|
|
if (aBit < 42) {
|
|
aBit += 6;
|
|
this.G++;
|
|
} else {
|
|
aBit = 0;
|
|
this.G = 0;
|
|
this.M++;
|
|
if (count > 0) {
|
|
this.loadAviaM(); // A = [M]
|
|
} else {
|
|
this.AROF = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} while (count);
|
|
|
|
this.Y = yc; // for display only
|
|
this.Z = zc; // for display only
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.fieldArithmetic = function fieldArithmetic(count, adding) {
|
|
/* Handles the Field Add (FAD) or Field Subtract (FSU) syllables.
|
|
"count" indicates the length of the fields to be operated upon.
|
|
"adding" will be false if this call is for FSU, otherwise it's for FAD */
|
|
var aBit; // A register bit nr
|
|
var bBit; // B register bit nr
|
|
var carry = 0; // carry/borrow bit
|
|
var compl = false; // complement addition (i.e., subtract the digits)
|
|
var TFFF; // local copy of MSFF/TFFF
|
|
var Q03F; // local copy of Q03F
|
|
var resultNegative; // sign of result is negative
|
|
var sd; // digit sum
|
|
var ycompl = false; // complement source digits
|
|
var yd; // source digit
|
|
var zcompl = false; // complement destination digits
|
|
var zd; // destination digit
|
|
|
|
this.compareSourceWithDest(count);
|
|
this.cycleCount += 2; // approximate the timing thus far
|
|
if (this.Q & 0x20) { // Q06F => count > 0, so there's characters to add
|
|
this.Q &= ~(0x28); // reset Q06F and Q04F
|
|
TFFF = (this.MSFF != 0); // get TFFF as a Boolean
|
|
Q03F = ((this.Q & 0x04) != 0); // get Q03F as a Boolean
|
|
|
|
// Back down the pointers to the last characters of their respective fields
|
|
if (this.K > 0) {
|
|
this.K--;
|
|
} else {
|
|
this.K = 7;
|
|
this.BROF = 0;
|
|
this.S--;
|
|
}
|
|
if (this.G > 0) {
|
|
this.G--;
|
|
} else {
|
|
this.G = 7;
|
|
this.AROF = 0;
|
|
this.M--;
|
|
}
|
|
|
|
if (!this.BROF) {
|
|
this.loadBviaS(); // B = [S]
|
|
}
|
|
if (!this.AROF) {
|
|
this.loadAviaM(); // A = [M]
|
|
}
|
|
|
|
this.Q |= 0x80; // set Q08F (for display only)
|
|
aBit = this.G*6; // A-bit number
|
|
bBit = this.K*6; // B-bit number
|
|
yd = (this.cc.fieldIsolate(this.A, aBit, 2) == 2 ? 2 : 0); // source sign
|
|
zd = (this.cc.fieldIsolate(this.B, bBit, 2) == 2 ? 2 : 0); // dest sign
|
|
compl = (yd == zd ? !adding : adding); // determine if complement needed
|
|
resultNegative = !( // determine sign of result
|
|
(zd == 0 && !compl) ||
|
|
(zd == 0 && Q03F && !TFFF) ||
|
|
(zd != 0 && compl && Q03F && TFFF) ||
|
|
(compl && !Q03F));
|
|
if (compl) {
|
|
this.Q |= 0x42; // set Q07F and Q02F (for display only)
|
|
carry = 1; // preset the carry/borrow bit (Q07F)
|
|
if (TFFF) {
|
|
this.Q |= 0x08; // set Q04F (for display only)
|
|
zcompl = true;
|
|
} else {
|
|
ycompl = true;
|
|
}
|
|
}
|
|
|
|
this.cycleCount += 4;
|
|
do {
|
|
count--;
|
|
this.cycleCount += 2;
|
|
yd = this.cc.fieldIsolate(this.A, aBit+2, 4); // get the source digit
|
|
zd = this.cc.fieldIsolate(this.B, bBit+2, 4); // get the dest digit
|
|
sd = (ycompl ? 9-yd : yd) + (zcompl ? 9-zd : zd) + carry; // develop binary digit sum
|
|
if (sd <= 9) {
|
|
carry = 0;
|
|
} else {
|
|
carry = 1;
|
|
sd -= 10;
|
|
}
|
|
if (resultNegative) {
|
|
sd += 0x20; // set sign (BA) bits in char to binary 10
|
|
resultNegative = false;
|
|
}
|
|
|
|
this.B = this.cc.fieldInsert(this.B, bBit, 6, sd);
|
|
|
|
if (count == 0) {
|
|
this.storeBviaS(); // [S] = B, store final dest word
|
|
} else {
|
|
if (bBit > 0) {
|
|
bBit -= 6;
|
|
this.K--;
|
|
} else {
|
|
bBit = 42;
|
|
this.K = 7;
|
|
this.storeBviaS(); // [S] = B
|
|
this.S--;
|
|
this.loadBviaS(); // B = [S]
|
|
}
|
|
if (aBit > 0) {
|
|
aBit -= 6;
|
|
this.G--;
|
|
} else {
|
|
aBit = 42;
|
|
this.G = 7;
|
|
this.M--;
|
|
this.loadAviaM(); // A = [M]
|
|
}
|
|
}
|
|
} while (count);
|
|
|
|
// Now restore the character pointers
|
|
count = this.H*8 + this.V;
|
|
while (count >= 8) {
|
|
count -= 8;
|
|
this.cycleCount++;
|
|
this.S++;
|
|
this.M++;
|
|
}
|
|
this.cycleCount += count;
|
|
while (count > 0) {
|
|
count--;
|
|
if (this.K < 7) {
|
|
this.K++;
|
|
} else {
|
|
this.K = 0;
|
|
this.S++;
|
|
}
|
|
if (this.G < 7) {
|
|
this.G++;
|
|
} else {
|
|
this.G = 0;
|
|
this.M++;
|
|
}
|
|
}
|
|
this.AROF = this.BROF = 0;
|
|
this.H = this.V = this.N = 0;
|
|
this.MSFF = (compl ? 1-carry : carry); // MSFF/TFFF = overflow indicator
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.streamBitsToDest = function streamBitsToDest(count, mask) {
|
|
/* Streams a pattern of bits to the destination specified by S, K, and V,
|
|
as supplied by the 48-bit "mask" argument. Partial words are filled from
|
|
the low-order bits of the mask. Implements the guts of Character-Mode
|
|
Bit Set (XX64) and Bit Reset (XX65). Leaves the registers pointing at the
|
|
next bit in sequence */
|
|
var bn; // field starting bit number
|
|
var fl; // field length in bits
|
|
|
|
if (count) {
|
|
this.cycleCount += count;
|
|
if (!this.BROF) {
|
|
this.loadBviaS(); // B = [S]
|
|
}
|
|
do {
|
|
bn = this.K*6 + this.V; // starting bit nr.
|
|
fl = 48-bn; // bits remaining in the word
|
|
if (count < fl) {
|
|
fl = count;
|
|
}
|
|
if (fl < 48) {
|
|
this.B = this.cc.fieldInsert(this.B, bn, fl, mask);
|
|
} else {
|
|
this.B = mask; // set the whole word
|
|
}
|
|
count -= fl; // decrement by number of bits modified
|
|
bn += fl; // increment the starting bit nr.
|
|
if (bn < 48) {
|
|
this.V = bn % 6;
|
|
this.K = (bn - this.V)/6;
|
|
} else {
|
|
this.K = this.V = 0;
|
|
this.storeBviaS(); // [S] = B, save the updated word
|
|
this.S++;
|
|
if (count > 0) {
|
|
this.loadBviaS(); // B = [S], fetch next word in sequence
|
|
} else {
|
|
this.BROF = 0;
|
|
}
|
|
}
|
|
} while (count);
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.streamProgramToDest = function streamProgramToDest(count) {
|
|
/* Implements the TRP (Transfer Program Characters) character-mode syllable */
|
|
var bBit; // B register bit nr
|
|
var bw; // current B register value
|
|
var c; // current character
|
|
var pBit; // P register bit nr
|
|
var pw; // current P register value
|
|
|
|
this.streamAdjustDestChar();
|
|
if (count) { // count > 0
|
|
if (!this.BROF) {
|
|
this.loadBviaS(); // B = [S]
|
|
}
|
|
if (!this.PROF) {
|
|
this.loadPviaC(); // fetch the program word, if necessary
|
|
}
|
|
this.cycleCount += count; // approximate the timing
|
|
pBit = (this.L*2 + (count % 2))*6; // P-reg bit number
|
|
pw = this.P;
|
|
bBit = this.K*6; // B-reg bit number
|
|
bw = this.B;
|
|
do {
|
|
c = this.cc.fieldIsolate(pw, pBit, 6);
|
|
bw = this.cc.fieldInsert(bw, bBit, 6, c);
|
|
count--;
|
|
if (bBit < 42) {
|
|
bBit += 6;
|
|
this.K++;
|
|
} else {
|
|
bBit = 0;
|
|
this.K = 0;
|
|
this.B = bw;
|
|
this.storeBviaS(); // [S] = B
|
|
this.S++;
|
|
if (count > 0 && count < 8) { // only need to load B if a partial word is left
|
|
this.loadBviaS(); // B = [S]
|
|
bw = this.B;
|
|
} else {
|
|
this.BROF = 0;
|
|
}
|
|
}
|
|
if (pBit < 42) {
|
|
pBit += 6;
|
|
if (!(count % 2)) {
|
|
this.L++;
|
|
}
|
|
} else {
|
|
pBit = 0;
|
|
this.L = 0;
|
|
this.C++;
|
|
this.loadPviaC(); // P = [C]
|
|
pw = this.P;
|
|
}
|
|
} while (count);
|
|
this.B = bw;
|
|
this.Y = c; // for display purposes only
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.streamSourceToDest = function streamSourceToDest(count, transform) {
|
|
/* General driver for character-mode character transfers from source to
|
|
destination, such as TRS or TRZ.
|
|
"count" is the number of source characters to transfer.
|
|
"transform" is a function(bBit, c, count) that determines how the characters
|
|
are transferred from the source (A) to destination (B) */
|
|
var aBit; // A register bit nr
|
|
var aw; // current A register word
|
|
var bBit; // B register bit nr
|
|
var c; // current character
|
|
|
|
this.streamAdjustSourceChar();
|
|
this.streamAdjustDestChar();
|
|
if (count) {
|
|
if (!this.BROF) {
|
|
this.loadBviaS(); // B = [S]
|
|
}
|
|
if (!this.AROF) {
|
|
this.loadAviaM(); // A = [M]
|
|
}
|
|
this.cycleCount += count*2; // approximate the timing
|
|
aBit = this.G*6; // A-bit number
|
|
aw = this.A;
|
|
bBit = this.K*6; // B-bit number
|
|
do {
|
|
c = this.cc.fieldIsolate(aw, aBit, 6);
|
|
transform.call(this, bBit, c, count)
|
|
count--;
|
|
if (bBit < 42) {
|
|
bBit += 6;
|
|
this.K++;
|
|
} else {
|
|
bBit = 0;
|
|
this.K = 0;
|
|
this.storeBviaS(); // [S] = B
|
|
this.S++;
|
|
if (count > 0 && count < 8) { // only need to load B if a partial word is left
|
|
this.loadBviaS(); // B = [S]
|
|
} else {
|
|
this.BROF = 0;
|
|
}
|
|
}
|
|
if (aBit < 42) {
|
|
aBit += 6;
|
|
this.G++;
|
|
} else {
|
|
aBit = 0;
|
|
this.G = 0;
|
|
this.M++;
|
|
if (count > 0) { // only need to load A if there's more to do
|
|
this.loadAviaM(); // A = [M]
|
|
aw = this.A;
|
|
} else {
|
|
this.AROF = 0;
|
|
}
|
|
}
|
|
} while (count);
|
|
this.Y = c; // for display purposes only
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.streamToDest = function streamToDest(count, transform) {
|
|
/* General driver for character-mode character operations on the destination
|
|
from a non-A register source, such as TBN.
|
|
"count" is the number of characters to transfer.
|
|
"transform" is a function(bBit, count) that determines how the characters
|
|
are stored to the destination (B). Returning truthy terminates the process
|
|
without incrementing the destination address */
|
|
var bBit; // B register bit nr
|
|
|
|
this.streamAdjustDestChar();
|
|
if (count) {
|
|
if (!this.BROF) {
|
|
this.loadBviaS(); // B = [S]
|
|
}
|
|
this.cycleCount += count*2; // approximate the timing
|
|
bBit = this.K*6; // B-bit number
|
|
do {
|
|
if (transform.call(this, bBit, count)) {
|
|
count = 0;
|
|
} else {
|
|
count--;
|
|
if (bBit < 42) {
|
|
bBit += 6;
|
|
this.K++;
|
|
} else {
|
|
bBit = 0;
|
|
this.K = 0;
|
|
this.storeBviaS(); // [S] = B
|
|
this.S++;
|
|
if (count > 0 && count < 8) { // only need to load B if a partial word is left
|
|
this.loadBviaS(); // B = [S]
|
|
} else {
|
|
this.BROF = 0;
|
|
}
|
|
}
|
|
}
|
|
} while (count);
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.streamInputConvert = function streamInputConvert(count) {
|
|
/* Converts a signed-numeric character field at the source M & G address
|
|
from decimal to binary, storing the resulting word at the S address and then
|
|
incrementing S. Normally, decimal to binary conversion shouldn't be this
|
|
complex, so we must do it more or less the way the B5500 hardware did, by
|
|
repeated remainder division (i.e., shifting right) and adjusting the
|
|
low-order digit by -3 when a one was shifted into the high-order bit of the
|
|
low-order digit from the higher digit locations. The problem with doing it a
|
|
more direct and efficient way is with digits that are not in the range 0-9.
|
|
Doing it the hardware way should yield the same (albeit questionable)
|
|
result. See Section 2.6 in the B5281 Training Manual for details. This
|
|
process took at least 27 clocks on the B5500, so we can afford to be slow
|
|
here, too. Note that a maximum of 8 characters are converted */
|
|
var a = 0; // local working copy of A
|
|
var b = 0; // local working copy of B
|
|
var power = 1; // A-register shif factor
|
|
|
|
this.streamAdjustSourceChar();
|
|
if (this.BROF) {
|
|
this.storeBviaS(); // [S] = B
|
|
this.BROF = 0;
|
|
}
|
|
if (this.K || this.V) { // adjust dest to word boundary
|
|
this.K = this.V = 0;
|
|
this.S++;
|
|
}
|
|
if (count) { // no conversion if count is zero
|
|
this.cycleCount += count*2 + 27;
|
|
count = ((count-1) & 0x07) + 1; // limit the count to 8
|
|
if (!this.AROF) {
|
|
this.loadAviaM(); // A = [M]
|
|
}
|
|
|
|
// First, assemble the digits into B as 4-bit BCD
|
|
do {
|
|
b = (b << 4) | ((this.Y = this.cc.fieldIsolate(this.A, this.G*6, 6)) & 0x0F);
|
|
if (this.G < 7) {
|
|
this.G++;
|
|
} else {
|
|
this.G = 0;
|
|
this.M++;
|
|
if (count > 1) {
|
|
this.loadAviaM(); // A = [M], only if more chars are needed
|
|
} else {
|
|
this.AROF = 0;
|
|
}
|
|
}
|
|
} while (--count);
|
|
|
|
// Then do the artful shifting to form the binary value in A
|
|
this.AROF = 0;
|
|
this.B = b; // for display purposes only
|
|
while (b) {
|
|
if (b & 0x01) {
|
|
a += power;
|
|
}
|
|
power *= 2;
|
|
b >>>= 1;
|
|
|
|
/* This next part is tricky, and was done by a switching network in the B5500.
|
|
When a 1 bit is shifted into the high-order position of a BCD decade from its
|
|
decade to the left, that bit has a place value of 8, but because the number
|
|
is decimal, it should have a place value of five. Therefore, in EACH such
|
|
decade, we need to subtract 3 to get the correct place value. The following
|
|
statement constructs a mask of 3s in each decade where the high-order bit is
|
|
set after the shift above, then subtracts that mask from the working B value.
|
|
See the discussion in Section 2.6 in the Training Manual cited above */
|
|
|
|
b -= ((b & 0x88888888) >>> 3)*3;
|
|
}
|
|
|
|
// Finally, fix up the binary sign and store the result
|
|
if (a) { // zero results have sign bit reset
|
|
if ((this.Y & 0x30) == 0x20) {
|
|
a += 0x400000000000; // set the sign bit
|
|
}
|
|
}
|
|
this.A = a;
|
|
this.storeAviaS(); // [S] = A
|
|
this.S++;
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.streamOutputConvert = function streamOutputConvert(count) {
|
|
/* Converts the binary word addressed by M (after word-boundary adjustment)
|
|
to decimal BIC at the destination address of S & K. The maximum number of
|
|
digits to convert is 8. If the binary value can be represented in "count"
|
|
digits (or the count is zero), the true-false FF, MSFF, is set; otherwise it
|
|
is reset. The sign is stored in low-order character of the result */
|
|
var a; // local working copy of A
|
|
var b = 0; // local working copy of B
|
|
var c; // converted decimal character
|
|
var d = 0; // digit counter
|
|
var power = 1; // power-of-64 factor for result digits
|
|
|
|
this.MSFF = 1; // set TFFF unless there's overflow
|
|
this.streamAdjustDestChar();
|
|
if (this.BROF) {
|
|
this.storeBviaS(); // [S] = B, but leave BROF set
|
|
}
|
|
if (this.G || this.H) { // adjust source to word boundary
|
|
this.G = this.H = 0;
|
|
this.AROF = 0;
|
|
this.M++;
|
|
}
|
|
if (count) { // count > 0
|
|
this.cycleCount += count*2 + 27;
|
|
if (!this.AROF) {
|
|
this.loadAviaM(); // A = [M]
|
|
}
|
|
count = ((count-1) & 0x07) + 1; // limit the count to 8
|
|
a = this.A % 0x8000000000; // get absolute mantissa value, ignore exponent
|
|
if (a) { // mantissa is non-zero, so conversion is required
|
|
if ((this.A % 0x800000000000) >= 0x400000000000) {
|
|
b = 0x20; // result is negative, so preset the sign in the low-order digit
|
|
}
|
|
do { // Convert the binary value in A to BIC digits in B
|
|
c = a % 10;
|
|
a = (a-c)/10;
|
|
if (c) {
|
|
b += c*power;
|
|
}
|
|
power *= 64;
|
|
} while (a && ++d < count);
|
|
if (a) {
|
|
this.MSFF = 0; // overflow occurred, so reset TFFF
|
|
}
|
|
}
|
|
this.AROF = 0; // invalidate A
|
|
this.M++; // and advance to the next source word
|
|
|
|
// Finally, stream the digits from A (whose value is still in local b) to the destination
|
|
this.A = b; // for display purposes only
|
|
this.loadBviaS(); // B = [S], restore original value of B
|
|
d = 48 - count*6; // starting bit in A
|
|
do {
|
|
this.B = this.cc.fieldTransfer(this.B, this.K*6, 6, b, d);
|
|
d += 6;
|
|
if (this.K < 7) {
|
|
this.K++;
|
|
} else {
|
|
this.storeBviaS(); // [S] = B
|
|
this.K = 0;
|
|
this.S++;
|
|
if (count > 1) {
|
|
this.loadBviaS(); // B = [S]
|
|
} else {
|
|
this.BROF = 0;
|
|
}
|
|
}
|
|
} while (--count);
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.storeForInterrupt = function storeForInterrupt(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
|
|
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; // if CM, get the correct TOS address from X
|
|
this.S = (this.X % 0x40000000) >>> 15;
|
|
this.X = this.X % 0x8000 +
|
|
temp * 0x8000 +
|
|
(this.X - this.X % 0x40000000);
|
|
if (saveAROF || forTest) {
|
|
this.S++;
|
|
this.storeAviaS(); // [S] = A
|
|
}
|
|
if (saveBROF || forTest) {
|
|
this.S++;
|
|
this.storeBviaS(); // [S] = B
|
|
}
|
|
this.B = this.X + // store CM Interrupt Loop-Control Word (ILCW)
|
|
saveAROF * 0x200000000000 +
|
|
0xC00000000000;
|
|
this.S++;
|
|
this.storeBviaS(); // [S] = B
|
|
} else {
|
|
if (saveBROF || forTest) {
|
|
this.S++;
|
|
this.storeBviaS(); // [S] = B
|
|
}
|
|
if (saveAROF || forTest) {
|
|
this.S++;
|
|
this.storeAviaS(); // [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.S++;
|
|
this.storeBviaS(); // [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.S++;
|
|
this.storeBviaS(); // [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.loadBviaS(); // B = [S]: get last RCW
|
|
this.S = (this.B % 0x40000000) >>> 15;
|
|
this.loadBviaS(); // B = [S]: get last MSCW
|
|
this.R = (this.B % 0x40000000000 - this.B % 0x200000000)/0x200000000; // B.[6:9]
|
|
this.S = this.F;
|
|
}
|
|
|
|
this.B = this.S + // build the Initiate Control Word (INCW)
|
|
this.CWMF * 0x8000 +
|
|
(this.TM & 0x1F) * 0x10000 +
|
|
this.Z * 0x400000 +
|
|
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
|
|
|
|
this.M = 0;
|
|
this.R = 0;
|
|
this.MSFF = 0;
|
|
this.SALF = 0;
|
|
this.BROF = 0;
|
|
this.AROF = 0;
|
|
if (forced) {
|
|
if (this.isP1) {
|
|
this.T = 0x89; // inject 0211=ITI into 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.C = this.B % 0x8000;
|
|
this.L = 0;
|
|
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
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.preset = function preset(runAddr) {
|
|
/* Presets the processor registers for a load condition at C=runAddr */
|
|
|
|
this.C = runAddr; // starting execution address
|
|
this.L = 1; // preset L to point to the second syllable
|
|
this.loadPviaC(); // load the program word to P
|
|
this.T = this.cc.fieldIsolate(this.P, 0, 12);
|
|
this.TROF = 1;
|
|
this.R = 0;
|
|
this.S = 0;
|
|
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.start = function start() {
|
|
/* Initiates the processor by scheduling it on the Javascript thread */
|
|
var stamp = new Date().getTime();
|
|
|
|
this.busy = 1;
|
|
this.procStart = stamp;
|
|
this.procTime -= stamp;
|
|
this.delayLastStamp = stamp;
|
|
this.delayRequested = 0;
|
|
this.scheduler = setCallback(this.schedule, this, 0);
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.stop = function stop() {
|
|
/* Stops running the processor on the Javascript thread */
|
|
var stamp = new Date().getTime();
|
|
|
|
this.T = 0;
|
|
this.TROF = 0; // idle the processor
|
|
this.PROF = 0;
|
|
this.busy = 0;
|
|
this.cycleLimit = 0; // exit this.run()
|
|
if (this.scheduler) {
|
|
clearCallback(this.scheduler);
|
|
this.scheduler = null;
|
|
}
|
|
while (this.procTime < 0) {
|
|
this.procTime += stamp;
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.initiate = function initiate(forTest) {
|
|
/* Initiates the processor from interrupt control words stored in the
|
|
stack. Assumes the INCW is in TOS. "forTest" implies use from IFT */
|
|
var bw; // local copy of B
|
|
var saveAROF = 0;
|
|
var saveBROF = 0;
|
|
var temp;
|
|
|
|
if (this.AROF) {
|
|
this.B = bw = this.A;
|
|
} else {
|
|
this.adjustBFull();
|
|
bw = this.B;
|
|
}
|
|
|
|
// restore the Initiate Control Word (INCW) or Initiate Test Control Word
|
|
this.S = bw % 0x8000;
|
|
this.CWMF = (bw % 0x10000) >>> 15;
|
|
if (forTest) {
|
|
this.TM = (bw % 0x100000 - bw % 0x10000)/0x10000 +
|
|
(bw % 0x200000 - bw % 0x100000)/0x100000 * 16 + // NCSF
|
|
(bw % 0x400000 - bw % 0x200000)/0x200000 * 32 + // CCCF
|
|
(bw % 0x100000000000 - bw % 0x80000000000)/0x80000000000 * 64 + // MWOF
|
|
(bw % 0x400000000000 - bw % 0x200000000000)/0x200000000000 * 128; // MROF
|
|
this.Z = (bw % 0x10000000 - bw % 0x400000)/0x400000;
|
|
this.Y = (bw % 0x400000000 - bw % 0x10000000)/0x10000000;
|
|
this.Q = (bw % 0x80000000000 - bw % 0x400000000)/0x400000000;
|
|
// Emulator doesn't support J register, so can't set that from TM
|
|
}
|
|
|
|
// restore the Interrupt Return Control Word (IRCW)
|
|
this.loadBviaS(); // B = [S]
|
|
this.S--;
|
|
bw = this.B;
|
|
this.C = bw % 0x8000;
|
|
this.F = (bw % 0x40000000) >>> 15;
|
|
this.K = (bw % 0x200000000 - bw % 0x40000000)/0x40000000;
|
|
this.G = (bw % 0x1000000000 - bw % 0x200000000)/0x200000000;
|
|
this.L = (bw % 0x4000000000 - bw % 0x1000000000)/0x1000000000;
|
|
this.V = (bw % 0x20000000000 - bw % 0x4000000000)/0x4000000000;
|
|
this.H = (bw % 0x100000000000 - bw % 0x20000000000)/0x20000000000;
|
|
this.loadPviaC(); // load program word to P
|
|
if (this.CWMF || forTest) {
|
|
saveBROF = (bw % 0x400000000000 - bw % 0x200000000000)/0x200000000000;
|
|
}
|
|
|
|
// restore the Interrupt Control Word (ICW)
|
|
this.loadBviaS(); // B = [S]
|
|
this.S--;
|
|
bw = this.B;
|
|
this.VARF = (bw % 0x2000000 - bw % 0x1000000)/0x1000000;
|
|
this.SALF = (bw % 0x80000000 - bw % 0x40000000)/0x40000000;
|
|
this.MSFF = (bw % 0x100000000 - bw % 0x80000000)/0x80000000;
|
|
this.R = (bw % 0x40000000000 - bw % 0x200000000)/0x200000000;
|
|
|
|
if (!(this.CWMF || forTest)) {
|
|
this.AROF = 0; // don't restore A or B for word mode --
|
|
this.BROF = 0; // they will pop up as necessary
|
|
} else {
|
|
this.M = bw % 0x8000;
|
|
this.N = (bw % 0x80000 - bw % 0x8000)/0x8000;
|
|
|
|
// restore the CM Interrupt Loop Control Word (ILCW)
|
|
this.loadBviaS(); // B = [S]
|
|
this.S--;
|
|
bw = this.B;
|
|
this.X = bw % 0x8000000000;
|
|
saveAROF = (bw % 0x400000000000 - bw % 0x200000000000)/0x200000000000;
|
|
|
|
// restore the B register
|
|
if (saveBROF || forTest) {
|
|
this.loadBviaS(); // B = [S]
|
|
this.S--;
|
|
}
|
|
|
|
// restore the A register
|
|
if (saveAROF || forTest) {
|
|
this.loadAviaS(); // A = [S]
|
|
this.S--;
|
|
}
|
|
|
|
this.AROF = saveAROF;
|
|
this.BROF = saveBROF;
|
|
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 +
|
|
(this.X - this.X % 0x40000000);
|
|
}
|
|
}
|
|
|
|
this.T = this.cc.fieldIsolate(this.P, this.L*12, 12);
|
|
this.TROF = 1;
|
|
if (!forTest) {
|
|
this.NCSF = 1;
|
|
} else {
|
|
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;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.initiateAsP2 = function initiateAsP2() {
|
|
/* Called from Central Control to initiate the processor as P2. Fetches the
|
|
INCW from @10 and calls initiate() */
|
|
|
|
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
|
|
this.T = 0x849; // inject 4111=IP1 into P2's T register
|
|
this.TROF = 1;
|
|
|
|
// Now start scheduling P2 on the Javascript thread
|
|
this.start();
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.singlePrecisionCompare = function singlePrecisionCompare() {
|
|
/* Algebraically compares the B register to the A register. Function returns
|
|
-1 if B<A, 0 if B=A, or +1 if B>A. Exits with AROF=0, BROF=1, and A and B as is */
|
|
var ea; // signed exponent of A
|
|
var eb; // signed exponent of B
|
|
var ma; // absolute mantissa of A
|
|
var mb; // absolute mantissa of B
|
|
var sa; // mantissa sign of A (0=positive)
|
|
var sb; // mantissa sign of B (ditto)
|
|
|
|
this.cycleCount += 4; // estimate some general overhead
|
|
this.adjustABFull();
|
|
this.AROF = 0; // A is unconditionally marked empty
|
|
ma = this.A % 0x8000000000; // extract the A mantissa
|
|
mb = this.B % 0x8000000000; // extract the B mantissa
|
|
|
|
// Extract the exponents and signs. If the exponents are unequal, normalize
|
|
// each until the high-order octade is non-zero or the exponents are equal.
|
|
if (ma == 0) { // if A mantissa is zero
|
|
ea = sa = 0; // consider A to be completely zero
|
|
} else {
|
|
ea = (this.A - ma)/0x8000000000;
|
|
sa = ((ea >>> 7) & 0x01);
|
|
ea = (ea & 0x40 ? -(ea & 0x3F) : (ea & 0x3F)) + 0x40;
|
|
}
|
|
if (mb == 0) { // if B mantissa is zero
|
|
eb = sb = 0; // consider B to be completely zero
|
|
} else {
|
|
eb = (this.B - mb)/0x8000000000;
|
|
sb = (eb >>> 7) & 0x01;
|
|
eb = (eb & 0x40 ? -(eb & 0x3F) : (eb & 0x3F)) + 0x40;
|
|
}
|
|
if (ma) { // normalize the A mantissa
|
|
while (ma < 0x1000000000 && ea != eb) {
|
|
this.cycleCount++;
|
|
ma *= 8; // shift left
|
|
ea--;
|
|
}
|
|
}
|
|
if (mb) { // normalize the B mantissa
|
|
while (mb < 0x1000000000 && eb != ea) {
|
|
this.cycleCount++;
|
|
mb *= 8; // shift left
|
|
eb--;
|
|
}
|
|
}
|
|
|
|
// Compare signs, exponents, and normalized magnitudes, in that order.
|
|
if (sb == sa) { // if signs are equal:
|
|
if (eb == ea) { // if exponents are equal:
|
|
if (mb == ma) { // if magnitudes are equal:
|
|
return 0; // then the operands are equal
|
|
} else if (mb > ma) { // otherwise, if magnitude of B > A:
|
|
return (sb ? -1 : 1); // B<A if B negative, B>A if B positive
|
|
} else { // otherwise, if magnitude of B < A:
|
|
return (sb ? 1 : -1); // B>A if B negative, B<A if B positive
|
|
}
|
|
} else if (eb > ea) { // otherwise, if exponent of B > A:
|
|
return (sb ? -1 : 1); // B<A if B negative, B>A if B positive
|
|
} else { // otherwise, if exponent of B < A
|
|
return (sb ? 1 : -1); // B>A if B negative, B<A if B positive
|
|
}
|
|
} else { // otherwise, if signs are different:
|
|
return (sa < sb ? -1 : 1); // B<A if B negative, B>A if B positive
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.singlePrecisionAdd = function singlePrecisionAdd(adding) {
|
|
/* Adds the contents of the A register to the B register, leaving the result
|
|
in B and invalidating A. If "adding" is not true, the sign of A is complemented
|
|
to accomplish subtraction instead of addition.
|
|
The B5500 did this by complement arithmetic, exchanging operands as necessary,
|
|
and maintaining a bunch of Q-register flags to keep it all straight. This
|
|
routine takes a more straightforward approach, doing algebraic arithmetic on
|
|
the A and B mantissas and maintaining separate extensions (X registers) for
|
|
scaling A and B. Only one register will be scaled, so the other extension will
|
|
always be zero */
|
|
var d = 0; // the guard (rounding) digit
|
|
var ea; // signed exponent of A
|
|
var eb; // signed exponent of B
|
|
var ma; // absolute mantissa of A
|
|
var mb; // absolute mantissa of B
|
|
var sa; // mantissa sign of A (0=positive)
|
|
var sb; // mantissa sign of B (ditto)
|
|
var xa = 0; // extension to A for scaling (pseudo X)
|
|
var xb = 0; // extension to B for scaling (pseudo X)
|
|
|
|
this.cycleCount += 4; // estimate some general overhead
|
|
this.adjustABFull();
|
|
this.AROF = 0; // A is unconditionally marked empty
|
|
ma = this.A % 0x8000000000; // extract the A mantissa
|
|
mb = this.B % 0x8000000000; // extract the B mantissa
|
|
|
|
if (ma == 0) { // if A mantissa is zero
|
|
if (mb == 0) { // and B mantissa is zero
|
|
this.B = 0; // result is all zeroes
|
|
} else {
|
|
this.B %= 0x800000000000; // otherwise, result is B with flag bit reset
|
|
}
|
|
} else if (mb == 0 && adding) { // otherwise, if B is zero and we're adding,
|
|
this.B = this.A % 0x800000000000; // result is A with flag bit reset
|
|
} else { // rats, we actually have to do this...
|
|
ea = (this.A - ma)/0x8000000000;
|
|
sa = (adding ? (ea >>> 7) & 0x01 : 1-((ea >>> 7) & 0x01));
|
|
ea = (ea & 0x40 ? -(ea & 0x3F) : (ea & 0x3F));
|
|
|
|
eb = (this.B - mb)/0x8000000000;
|
|
sb = (eb >>> 7) & 0x01;
|
|
eb = (eb & 0x40 ? -(eb & 0x3F) : (eb & 0x3F));
|
|
|
|
// If the exponents are unequal, normalize the larger and scale the smaller
|
|
// until they are in alignment, or one of the mantissas (mantissae?) becomes zero
|
|
if (ea > eb) {
|
|
// Normalize A for 39 bits (13 octades)
|
|
while (ma < 0x1000000000 && ea != eb) {
|
|
this.cycleCount++;
|
|
ma *= 8; // shift left
|
|
ea--;
|
|
}
|
|
// Scale B until its exponent matches or mantissa goes to zero
|
|
while (ea != eb) {
|
|
this.cycleCount++;
|
|
d = mb % 8;
|
|
mb = (mb - d)/8; // shift right into extension
|
|
xb = (xb - xb%8)/8 + d*0x1000000000;
|
|
eb++;
|
|
if (mb == 0 && ea != eb) {
|
|
eb = ea; // if B=0, kill the scaling loop: result will have exponent of A
|
|
xb = 0; // prevent rounding of result
|
|
}
|
|
}
|
|
} else if (ea < eb) {
|
|
// Normalize B for 39 bits (13 octades)
|
|
while (mb < 0x1000000000 && eb != ea) {
|
|
this.cycleCount++;
|
|
mb *= 8; // shift left
|
|
eb--;
|
|
}
|
|
// Scale A until its exponent matches or mantissa goes to zero
|
|
while (eb != ea) {
|
|
this.cycleCount++;
|
|
d = ma % 8;
|
|
ma = (ma - d)/8; // shift right into extension
|
|
xa = (xa - xa%8)/8 + d*0x1000000000;
|
|
ea++;
|
|
if (ma == 0 && eb != ea) {
|
|
ea = eb; // if A=0, kill the scaling loop
|
|
xa = 0; // prevent rounding of result
|
|
}
|
|
}
|
|
}
|
|
|
|
// At this point, the exponents are aligned (or one of the mantissas
|
|
// is zero), so do the actual 39-bit additions of mantissas and extensions
|
|
|
|
xb = (sb ? -xb : xb) + (sa ? -xa : xa); // compute the extension
|
|
if (xb < 0) {
|
|
xb += 0x8000000000; // adjust for underflow in the extension
|
|
d = -1; // adjust B for borrow into extension
|
|
} else if (xb > 0x8000000000) {
|
|
xb -= 0x8000000000; // adjust for overflow in the extension
|
|
d = 1; // adjust B for carry from extension
|
|
} else {
|
|
d = 0; // no adjustment
|
|
}
|
|
|
|
mb = (sb ? -mb : mb) + (sa ? -ma : ma) + d; // compute the mantissa
|
|
if (mb >= 0) { // if non-negative...
|
|
sb = 0; // reset the B sign bit
|
|
} else { // if negative...
|
|
sb = 1; // set the B sign bit
|
|
mb = -mb; // negate the B mantissa
|
|
if (xb) { // if non-zero octades have been shifted into X (and ONLY if... learned THAT the hard way...)
|
|
xb = 0x8000000000 - xb; // negate the extension in X
|
|
mb--; // and adjust for borrow into X
|
|
}
|
|
}
|
|
|
|
// Normalize and round as necessary
|
|
if (mb < 0x1000000000) { // Normalization can be required for subtract
|
|
if (xb < 0x800000000) { // if first two octades in X < @04 then
|
|
d = 0; // no rounding will take place
|
|
} else {
|
|
this.cycleCount++;
|
|
d = (xb - xb%0x1000000000)/0x1000000000; // get the high-order digit from X
|
|
xb = (xb%0x1000000000)*8; // shift B and X left together
|
|
mb = mb*8 + d;
|
|
eb--;
|
|
d = (xb - xb%0x1000000000)/0x1000000000; // get the rounding digit from X
|
|
}
|
|
} else if (mb >= 0x8000000000) { // Scaling can be required for add
|
|
this.cycleCount++;
|
|
d = mb % 8; // get the rounding digit from B
|
|
mb = (mb - d)/8; // shift right due to overflow
|
|
eb++;
|
|
} else {
|
|
d = (xb - xb%0x1000000000)/0x1000000000; // another hard-earned lesson...
|
|
}
|
|
|
|
// Note: the Training Manual does not say that rounding is suppressed
|
|
// for add/subtract when the mantissa is all ones, but it does say so
|
|
// for multiply/divide, so we assume it's also the case here.
|
|
if (d & 0x04) { // if the guard digit >= 4
|
|
if (mb < 0x7FFFFFFFFF) { // and rounding would not cause overflow
|
|
this.cycleCount++;
|
|
mb++; // round up the result
|
|
}
|
|
}
|
|
|
|
// Check for exponent overflow
|
|
if (eb > 63) {
|
|
eb %= 64;
|
|
if (this.NCSF) {
|
|
this.I = (this.I & 0x0F) | 0xB0; // set I05/6/8: exponent-overflow
|
|
this.cc.signalInterrupt();
|
|
}
|
|
} else if (eb < 0) {
|
|
eb = (-eb) | 0x40; // set the exponent sign bit
|
|
}
|
|
|
|
this.X = xb; // for display purposes only
|
|
if (mb == 0) { // if the mantissa is zero...
|
|
this.B = 0; // the whole result is zero, and we're done
|
|
} else { // otherwise, determine the resulting sign
|
|
this.B = (sb*128 + eb)*0x8000000000 + mb; // Final Answer
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.singlePrecisionMultiply = function singlePrecisionMultiply() {
|
|
/* Multiplies the contents of the A register to the B register, leaving the
|
|
result in B and invalidating A. A double-precision mantissa is developed and
|
|
then normalized and rounded */
|
|
var d; // current multiplier digit (octal)
|
|
var ea; // signed exponent of A
|
|
var eb; // signed exponent of B
|
|
var ma; // absolute mantissa of A
|
|
var mb; // absolute mantissa of B
|
|
var mx = 0; // local copy of X for product extension
|
|
var n; // local copy of N (octade counter)
|
|
var sa; // mantissa sign of A (0=positive)
|
|
var sb; // mantissa sign of B (ditto)
|
|
var xx; // local copy of X for multiplier
|
|
|
|
this.cycleCount += 4; // estimate some general overhead
|
|
this.adjustABFull();
|
|
this.AROF = 0; // A is unconditionally marked empty
|
|
ma = this.A % 0x8000000000; // extract the A mantissa
|
|
mb = this.B % 0x8000000000; // extract the B mantissa
|
|
|
|
if (ma == 0) { // if A mantissa is zero
|
|
this.B = 0; // result is all zeroes
|
|
} else if (mb == 0) { // otherwise, if B is zero,
|
|
this.B = 0; // result is all zeroes
|
|
} else { // otherwise, let the games begin
|
|
ea = (this.A - ma)/0x8000000000;
|
|
sa = (ea >>> 7) & 0x01;
|
|
ea = (ea & 0x40 ? -(ea & 0x3F) : (ea & 0x3F));
|
|
|
|
eb = (this.B - mb)/0x8000000000;
|
|
sb = (eb >>> 7) & 0x01;
|
|
eb = (eb & 0x40 ? -(eb & 0x3F) : (eb & 0x3F));
|
|
|
|
// If the exponents are BOTH zero, perform an integer multiply.
|
|
// Otherwise, normalize both operands
|
|
if (ea == 0 && eb == 0) {
|
|
this.Q |= 0x10; // integer multiply operation: set Q05F
|
|
} else {
|
|
// Normalize A for 39 bits (13 octades)
|
|
while (ma < 0x1000000000) {
|
|
this.cycleCount++;
|
|
ma *= 8; // shift left
|
|
ea--;
|
|
}
|
|
// Normalize B for 39 bits (13 octades)
|
|
while (mb < 0x1000000000) {
|
|
this.cycleCount++;
|
|
mb *= 8; // shift left
|
|
eb--;
|
|
}
|
|
}
|
|
|
|
// Determine resulting mantissa sign; initialize the product
|
|
sb ^= sa; // positive if signs are same, negative if different
|
|
xx = mb; // move multiplier to X
|
|
mb = 0; // initialize high-order part of product
|
|
|
|
// Now we step through the 13 octades of the multiplier, developing the product
|
|
for (n=0; n<13; n++) {
|
|
d = xx % 8; // extract the current multiplier digit
|
|
xx = (xx - d)/8; // shift the multiplier right one octade
|
|
|
|
if (d == 0) { // if multiplier digit is zero
|
|
this.cycleCount++; // hardware optimizes this case
|
|
} else {
|
|
this.cycleCount += 3; // just estimate the average number of clocks
|
|
mb += ma*d; // develop the partial product
|
|
}
|
|
|
|
d = mb % 8; // get the low-order octade of partial product in B
|
|
mb = (mb - d)/8; // shift B right one octade
|
|
mx = mx/8 + d*0x1000000000; // shift B octade into high-order end of extension
|
|
} // for (n)
|
|
|
|
// Normalize the result
|
|
if (this.Q & 0x10 && mb == 0) { // if it's integer multiply (Q05F) with integer result
|
|
mb = mx; // just use the low-order 39 bits
|
|
mx = 0;
|
|
eb = 0; // and don't normalize
|
|
} else {
|
|
eb += ea+13; // compute resulting exponent from multiply
|
|
while (mb < 0x1000000000) {
|
|
this.cycleCount++;
|
|
ma = mx % 0x1000000000; // reuse ma: get low-order 36 bits of mantissa extension
|
|
d = (mx - ma)/0x1000000000; // get high-order octade of extension
|
|
mb = mb*8 + d; // shift high-order extension octade into B
|
|
mx = ma*8; // shift extension left one octade
|
|
eb--;
|
|
}
|
|
}
|
|
|
|
// Round the result
|
|
this.Q &= ~(0x10); // reset Q05F
|
|
this.A = 0; // required by specs due to the way rounding addition worked
|
|
|
|
if (mx >= 0x4000000000) { // if high-order bit of remaining extension is 1
|
|
this.Q |= 0x01; // set Q01F (for display purposes only)
|
|
if (mb < 0x7FFFFFFFFF) { // if the rounding would not cause overflow
|
|
this.cycleCount++;
|
|
mb++; // round up the result
|
|
}
|
|
}
|
|
|
|
if (mb == 0) { // don't see how this could be necessary here, but
|
|
this.B = 0; // the TM says to do it anyway
|
|
} else {
|
|
// Check for exponent under/overflow
|
|
if (eb > 63) {
|
|
eb %= 64;
|
|
if (this.NCSF) {
|
|
this.I = (this.I & 0x0F) | 0xB0; // set I05/6/8: exponent-overflow
|
|
this.cc.signalInterrupt();
|
|
}
|
|
} else if (eb < 0) {
|
|
if (eb >= -63) {
|
|
eb = (-eb) | 0x40; // set the exponent sign bit
|
|
} else {
|
|
eb = ((-eb) % 64) | 0x40; // mod the exponent and set its sign
|
|
if (this.NCSF) {
|
|
this.I = (this.I & 0x0F) | 0xA0;// set I06/8: exponent-underflow
|
|
this.cc.signalInterrupt();
|
|
}
|
|
}
|
|
}
|
|
|
|
this.B = (sb*128 + eb)*0x8000000000 + mb; // Final Answer
|
|
}
|
|
}
|
|
this.X = mx; // for display purposes only
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.singlePrecisionDivide = function singlePrecisionDivide() {
|
|
/* Divides the contents of the A register into the B register, leaving the
|
|
result in B and invalidating A. A 14-octade mantissa is developed and
|
|
then normalized and rounded */
|
|
var ea; // signed exponent of A
|
|
var eb; // signed exponent of B
|
|
var ma; // absolute mantissa of A
|
|
var mb; // absolute mantissa of B
|
|
var n = 0; // local copy of N (octade counter)
|
|
var q = 0; // current quotient digit (octal)
|
|
var sa; // mantissa sign of A (0=positive)
|
|
var sb; // mantissa sign of B (ditto)
|
|
var xx = 0; // local copy of X for quotient development
|
|
|
|
this.cycleCount += 4; // estimate some general overhead
|
|
this.adjustABFull();
|
|
this.AROF = 0; // A is unconditionally marked empty
|
|
ma = this.A % 0x8000000000; // extract the A mantissa
|
|
mb = this.B % 0x8000000000; // extract the B mantissa
|
|
|
|
if (ma == 0) { // if A mantissa is zero
|
|
this.A = this.B = 0; // result is all zeroes
|
|
if (this.NCSF) {
|
|
this.I = (this.I & 0x0F) | 0xD0; // set I05/7/8: divide by zero
|
|
this.cc.signalInterrupt();
|
|
}
|
|
} else if (mb == 0) { // otherwise, if B is zero,
|
|
this.A = this.B = 0; // result is all zeroes
|
|
} else { // otherwise, may the octades always be in your favor
|
|
ea = (this.A - ma)/0x8000000000;
|
|
sa = (ea >>> 7) & 0x01;
|
|
ea = (ea & 0x40 ? -(ea & 0x3F) : (ea & 0x3F));
|
|
|
|
eb = (this.B - mb)/0x8000000000;
|
|
sb = (eb >>> 7) & 0x01;
|
|
eb = (eb & 0x40 ? -(eb & 0x3F) : (eb & 0x3F));
|
|
|
|
// Normalize A for 39 bits (13 octades)
|
|
while (ma < 0x1000000000) {
|
|
this.cycleCount++;
|
|
ma *= 8; // shift left
|
|
ea--;
|
|
}
|
|
// Normalize B for 39 bits (13 octades)
|
|
while (mb < 0x1000000000) {
|
|
this.cycleCount++;
|
|
mb *= 8; // shift left
|
|
eb--;
|
|
}
|
|
|
|
sb ^= sa; // positive if signs are same, negative if different
|
|
|
|
// Now we step through the development of the quotient one octade at a time,
|
|
// tallying the shifts in n until the high-order octade of xx is non-zero (i.e.,
|
|
// normalized). The divisor is in ma and the dividend (which becomes the
|
|
// remainder) is in mb. Since the operands are normalized, this will take
|
|
// either 13 or 14 shifts. We do the xx shift at the top of the loop so that
|
|
// the 14th (rounding) digit will be available in q at the end. The initial
|
|
// shift has no effect, as it operates using zero values for xx and q.
|
|
do {
|
|
xx = xx*8 + q; // shift quotient digit into the working quotient
|
|
n++; // tally the shifts (one more than affects result)
|
|
q = 0; // initialize the quotient digit
|
|
while (mb >= ma) {
|
|
q++; // bump the quotient digit
|
|
mb -= ma; // subtract divisor from remainder
|
|
}
|
|
mb *= 8; // shift the remainder left one octade
|
|
} while (xx < 0x1000000000);
|
|
|
|
this.cycleCount += n*3; // just estimate the average number of divide clocks
|
|
eb -= ea + n - 2; // compute the exponent, accounting for the extra shift
|
|
|
|
// Round the result (it's already normalized)
|
|
this.A = 0; // required by specs due to the way rounding addition worked
|
|
if (q >= 4) { // if high-order bit of last quotient digit is 1
|
|
this.Q |= 0x01; // set Q01F (for display purposes only)
|
|
if (xx < 0x7FFFFFFFFF) { // if the rounding would not cause overflow
|
|
xx++; // round up the result
|
|
}
|
|
}
|
|
|
|
// Check for exponent under/overflow
|
|
if (eb > 63) {
|
|
eb %= 64;
|
|
if (this.NCSF) {
|
|
this.I = (this.I & 0x0F) | 0xB0; // set I05/6/8: exponent-overflow
|
|
this.cc.signalInterrupt();
|
|
}
|
|
} else if (eb < 0) {
|
|
if (eb >= -63) {
|
|
eb = (-eb) | 0x40; // set the exponent sign bit
|
|
} else {
|
|
eb = ((-eb) % 64) | 0x40; // mod the exponent and set its sign
|
|
if (this.NCSF) {
|
|
this.I = (this.I & 0x0F) | 0xA0;// set I06/8: exponent-underflow
|
|
this.cc.signalInterrupt();
|
|
}
|
|
}
|
|
}
|
|
|
|
this.B = (sb*128 + eb)*0x8000000000 + xx; // Final Answer
|
|
}
|
|
this.X = xx; // for display purposes only
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.integerDivide = function integerDivide() {
|
|
/* Divides the contents of the A register into the B register, leaving the
|
|
integerized result in B and invalidating A. If the result cannot be expressed
|
|
as an integer, the Integer-Overflow interrupt is set */
|
|
var ea; // signed exponent of A
|
|
var eb; // signed exponent of B
|
|
var ma; // absolute mantissa of A
|
|
var mb; // absolute mantissa of B
|
|
var n = 0; // local copy of N (octade counter)
|
|
var q = 0; // current quotient digit (octal)
|
|
var sa; // mantissa sign of A (0=positive)
|
|
var sb; // mantissa sign of B (ditto)
|
|
var xx = 0; // local copy of X for quotient development
|
|
|
|
this.cycleCount += 4; // estimate some general overhead
|
|
this.adjustABFull();
|
|
this.AROF = 0; // A is unconditionally marked empty
|
|
ma = this.A % 0x8000000000; // extract the A mantissa
|
|
mb = this.B % 0x8000000000; // extract the B mantissa
|
|
|
|
if (ma == 0) { // if A mantissa is zero
|
|
this.A = this.B = 0; // result is all zeroes
|
|
if (this.NCSF) {
|
|
this.I = (this.I & 0x0F) | 0xD0; // set I05/7/8: divide by zero
|
|
this.cc.signalInterrupt();
|
|
}
|
|
} else if (mb == 0) { // otherwise, if B is zero,
|
|
this.A = this.B = 0; // result is all zeroes
|
|
} else { // otherwise, continue
|
|
ea = (this.A - ma)/0x8000000000;
|
|
sa = ((ea >>> 7) & 0x01);
|
|
ea = (ea & 0x40 ? -(ea & 0x3F) : (ea & 0x3F));
|
|
|
|
eb = (this.B - mb)/0x8000000000;
|
|
sb = (eb >>> 7) & 0x01;
|
|
eb = (eb & 0x40 ? -(eb & 0x3F) : (eb & 0x3F));
|
|
|
|
// Normalize A for 39 bits (13 octades)
|
|
while (ma < 0x1000000000) {
|
|
this.cycleCount++;
|
|
ma *= 8; // shift left
|
|
ea--;
|
|
}
|
|
// Normalize B for 39 bits (13 octades)
|
|
while (mb < 0x1000000000) {
|
|
this.cycleCount++;
|
|
mb *= 8; // shift left
|
|
eb--;
|
|
}
|
|
|
|
if (ea > eb) { // if divisor has greater magnitude
|
|
this.A = this.B = 0; // quotient is < 1, so set result to zero
|
|
} else { // otherwise, do the long division
|
|
sb ^= sa; // positive if signs are same, negative if different
|
|
|
|
// 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
|
|
// extra quotient digit.
|
|
do {
|
|
this.cycleCount += 3; // just estimate the average number of clocks
|
|
q = 0; // initialize the quotient digit
|
|
while (mb >= ma) {
|
|
q++; // bump the quotient digit
|
|
mb -= ma; // subtract divisor from remainder
|
|
}
|
|
mb *= 8; // shift the remainder left one octade
|
|
xx = xx*8 + q; // shift quotient digit into the working quotient
|
|
if (xx >= 0x1000000000) {
|
|
break; // quotient has become normalized
|
|
} else if (ea < eb) {
|
|
eb--; // decrement the B exponent
|
|
} else {
|
|
break;
|
|
}
|
|
} while (true);
|
|
|
|
if (ea == eb) {
|
|
eb = 0; // integer result developed
|
|
} else {
|
|
if (this.NCSF) { // integer overflow result
|
|
this.I = (this.I & 0x0F) | 0xC0; // set I07/8: integer-overflow
|
|
this.cc.signalInterrupt();
|
|
}
|
|
eb = (eb-ea)%64;
|
|
if (eb < 0) {
|
|
eb = (-eb) | 0x40; // set the exponent sign bit
|
|
}
|
|
}
|
|
|
|
this.A = 0; // required by specs
|
|
this.B = (sb*128 + eb)*0x8000000000 + xx; // Final Answer
|
|
}
|
|
}
|
|
this.X = xx; // for display purposes only
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.remainderDivide = function remainderDivide() {
|
|
/* Divides the contents of the A register into the B register, leaving the
|
|
remainder result in B and invalidating A. The sign of the result is the sign
|
|
of the dividend (B register value). If the quotient cannot be expressed as an
|
|
integer, the Integer-Overflow interrupt is set */
|
|
var ea; // signed exponent of A
|
|
var eb; // signed exponent of B
|
|
var ma; // absolute mantissa of A
|
|
var mb; // absolute mantissa of B
|
|
var n = 0; // local copy of N (octade counter)
|
|
var q = 0; // current quotient digit (octal)
|
|
var sa; // mantissa sign of A (0=positive)
|
|
var sb; // mantissa sign of B (ditto)
|
|
var xx = 0; // local copy of X for quotient development
|
|
|
|
this.cycleCount += 4; // estimate some general overhead
|
|
this.adjustABFull();
|
|
this.AROF = 0; // A is unconditionally marked empty
|
|
ma = this.A % 0x8000000000; // extract the A mantissa
|
|
mb = this.B % 0x8000000000; // extract the B mantissa
|
|
|
|
if (ma == 0) { // if A mantissa is zero
|
|
this.A = this.B = 0; // result is all zeroes
|
|
if (this.NCSF) {
|
|
this.I = (this.I & 0x0F) | 0xD0; // set I05/7/8: divide by zero
|
|
this.cc.signalInterrupt();
|
|
}
|
|
} else if (mb == 0) { // otherwise, if B is zero,
|
|
this.A = this.B = 0; // result is all zeroes
|
|
} else { // otherwise, continue
|
|
ea = (this.A - ma)/0x8000000000;
|
|
sa = (ea >>> 7) & 0x01;
|
|
ea = (ea & 0x40 ? -(ea & 0x3F) : (ea & 0x3F));
|
|
|
|
eb = (this.B - mb)/0x8000000000;
|
|
sb = (eb >>> 7) & 0x01;
|
|
eb = (eb & 0x40 ? -(eb & 0x3F) : (eb & 0x3F));
|
|
|
|
// Normalize A for 39 bits (13 octades)
|
|
while (ma < 0x1000000000) {
|
|
this.cycleCount++;
|
|
ma *= 8; // shift left
|
|
ea--;
|
|
}
|
|
// Normalize B for 39 bits (13 octades)
|
|
while (mb < 0x1000000000) {
|
|
this.cycleCount++;
|
|
mb *= 8; // shift left
|
|
eb--;
|
|
}
|
|
|
|
if (ea > eb) { // if divisor has greater magnitude
|
|
this.A = 0; // quotient is < 1, so set A to zero and
|
|
this.B %= 0x8000000000000; // result is original B (less the flag bit)
|
|
} else { // otherwise, work remains (so to speak)
|
|
// 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
|
|
// extra quotient digit.
|
|
do {
|
|
this.cycleCount += 3; // just estimate the average number of clocks
|
|
q = 0; // initialize the quotient digit
|
|
while (mb >= ma) {
|
|
q++; // bump the quotient digit
|
|
mb -= ma; // subtract divisor from remainder
|
|
}
|
|
xx = xx*8 + q; // shift quotient digit into the working quotient
|
|
if (xx >= 0x1000000000) {
|
|
break; // quotient has become normalized
|
|
} else if (ea < eb) {
|
|
mb *= 8; // shift the remainder left one octade
|
|
eb--; // decrement the B exponent
|
|
} else {
|
|
break;
|
|
}
|
|
} while (true);
|
|
|
|
if (eb < -63) { // check for exponent underflow
|
|
eb %= 64; // if so, exponent is mod 64
|
|
if (this.NCSF) {
|
|
this.I = (this.I & 0x0F) | 0xA0; // set I06/8: exponent-underflow
|
|
this.cc.signalInterrupt();
|
|
}
|
|
} else if (ea == eb) { // integer result developed
|
|
if (mb == 0) { // if B mantissa is zero, then
|
|
eb = sb = 0; // assure result will be all zeroes
|
|
} else {
|
|
eb %= 64; // use remainder exponent mod 64
|
|
}
|
|
} else {
|
|
if (this.NCSF) { // integer overflow result
|
|
this.I = (this.I & 0x0F) | 0xC0; // set I07/8: integer-overflow
|
|
this.cc.signalInterrupt();
|
|
}
|
|
mb = eb = sb = 0; // result in B will be all zeroes
|
|
}
|
|
if (eb < 0) {
|
|
eb = (-eb) | 0x40; // set the exponent sign bit
|
|
}
|
|
|
|
this.A = 0; // required by specs
|
|
this.B = (sb*128 + eb)*0x8000000000 + mb; // Final Answer
|
|
}
|
|
}
|
|
this.X = xx; // for display purposes only
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.doublePrecisionAdd = function doublePrecisionAdd(adding) {
|
|
/* Adds the double-precision contents of the A and B registers to the double-
|
|
precision contents of the top two words in the memory stack, leaving the result
|
|
in A and B. If "adding" is not true, the sign of A is complemented to accomplish
|
|
subtraction instead of addition */
|
|
var d; // shifting digit between registers
|
|
var ea; // signed exponent of A
|
|
var eb; // signed exponent of B
|
|
var ma; // absolute mantissa of A
|
|
var mb; // absolute mantissa of B
|
|
var sa; // mantissa sign of A (0=positive)
|
|
var sb; // mantissa sign of B (ditto)
|
|
var xa; // extended mantissa for A
|
|
var xb; // extended mantissa for B
|
|
|
|
// Estimate some general overhead and account for stack manipulation we don't do
|
|
this.cycleCount += B5500CentralControl.memWriteCycles*4 + 8;
|
|
|
|
this.adjustABFull(); // extract the top (A) operand fields:
|
|
ma = this.A % 0x8000000000; // extract the A mantissa
|
|
xa = this.B % 0x8000000000; // extract the A mantissa extension
|
|
ea = (this.A - ma)/0x8000000000;
|
|
sa = (adding ? (ea >>> 7) & 0x01 : 1-((ea >>> 7) & 0x01));
|
|
ea = (ea & 0x40 ? -(ea & 0x3F) : (ea & 0x3F));
|
|
|
|
this.AROF = this.BROF = 0; // empty the TOS registers
|
|
this.adjustABFull(); // extract the second (B) operand fields:
|
|
mb = this.A % 0x8000000000; // extract the B mantissa
|
|
xb = this.B % 0x8000000000; // extract the B mantissa extension
|
|
eb = (this.B - mb)/0x8000000000;
|
|
sb = (eb >>> 7) & 0x01;
|
|
eb = (eb & 0x40 ? -(eb & 0x3F) : (eb & 0x3F));
|
|
|
|
|
|
if (ma == 0 && xa == 0) { // if A is zero
|
|
if (mb == 0 && xb == 0) { // and B is zero
|
|
this.A = this.B = 0; // result is all zeroes
|
|
} else {
|
|
this.A %= 0x800000000000; // otherwise, result is B with flag bit reset
|
|
}
|
|
} else if (mb == 0 && xb == 0 && adding) { // otherwise, if B is zero and we're adding,
|
|
this.B = xa; // reconstruct A operand with flag bit reset
|
|
this.A = ((sa*2 + (ea < 0 ? 1 : 0))*64 + (ea < 0 ? -ea : ea))*0x8000000000 + ma;
|
|
} else { // so much for the simple cases...
|
|
// If the exponents are unequal, normalize the larger and scale the smaller
|
|
// until they are in alignment, or one of the mantissas becomes zero
|
|
if (ea > eb) {
|
|
// Normalize A for 78 bits (26 octades)
|
|
while (ma < 0x1000000000 && ea != eb) {
|
|
this.cycleCount++;
|
|
d = (xa - xa%0x1000000000)/0x1000000000;
|
|
ma = ma*8 + d; // shift left
|
|
xa = (xa % 0x1000000000)*8;
|
|
ea--;
|
|
}
|
|
// Scale B until its exponent matches or mantissa goes to zero
|
|
while (ea != eb) {
|
|
this.cycleCount++;
|
|
d = mb % 8;
|
|
mb = (mb - d)/8; // shift right into extension
|
|
xb = (xb - xb%8)/8 + d*0x1000000000;
|
|
if (mb && xb) {
|
|
eb++;
|
|
} else {
|
|
eb = ea; // if B=0, result will have exponent of A
|
|
}
|
|
}
|
|
} else if (ea < eb) {
|
|
// Normalize B for 78 bits (26 octades)
|
|
while (mb < 0x1000000000 && eb != ea) {
|
|
this.cycleCount++;
|
|
d = (xb - xb%0x1000000000)/0x1000000000;
|
|
mb = mb*8 + d; // shift left
|
|
xb = (xb % 0x1000000000)*8;
|
|
eb--;
|
|
}
|
|
// Scale A until its exponent matches or mantissa goes to zero
|
|
while (eb != ea) {
|
|
this.cycleCount++;
|
|
d = ma % 8;
|
|
ma = (ma - d)/8; // shift right into extension
|
|
xa = (xa - xa%8)/8 + d*0x1000000000;
|
|
if (ma && xa) {
|
|
ea++;
|
|
} else {
|
|
ea = eb; // if A=0, kill the scaling loop
|
|
}
|
|
}
|
|
}
|
|
|
|
// At this point, the exponents are aligned (or one of the mantissas
|
|
// is zero), so do the actual 78-bit addition.
|
|
|
|
xb = (sb ? -xb : xb) + (sa ? -xa : xa); // compute the extension
|
|
if (xb < 0) {
|
|
xb += 0x8000000000; // adjust for underflow in the extension
|
|
mb += (sb ? 1 : -1); // adjust B for borrow into extension
|
|
} else if (xb > 0x8000000000) {
|
|
xb -= 0x8000000000; // adjust for overflow in the extension
|
|
mb += (sb? -1 : 1); // adjust B for carry from extension
|
|
}
|
|
|
|
mb = (sb ? -mb : mb) + (sa ? -ma : ma); // compute the mantissa
|
|
if (mb > 0) { // if positive...
|
|
sb = 0; // reset the B sign bit
|
|
} else { // if negative...
|
|
sb = 1; // set the B sign bit
|
|
mb = -mb; // negate the B mantissa
|
|
if (xb) { // if the extension is non-zero
|
|
xb = 0x8000000000 - xb; // negate the extension
|
|
mb--; // and adjust for borrow into the extension
|
|
}
|
|
}
|
|
|
|
// Scale or normalize as necessary
|
|
if (mb >= 0x8000000000) { // If overflowed 39 bits, scale result
|
|
this.cycleCount++;
|
|
d = mb % 8; // get the rounding digit from B
|
|
mb = (mb - d)/8; // shift mantissa right due to overflow
|
|
xb = (xb - xb%8)/8 + d*0x1000000000; // shift extension right and insert mantissa digit
|
|
eb++;
|
|
} else { // Otherwise, normalize as necessary
|
|
while (mb < 0x1000000000 && mb && xb) {
|
|
this.cycleCount++;
|
|
d = (xb - xb%0x1000000000)/0x1000000000; // get the high-order digit from the extension
|
|
xb = (xb%0x1000000000)*8; // shift B and X left together
|
|
mb = mb*8 + d;
|
|
eb--;
|
|
}
|
|
}
|
|
|
|
if (mb == 0 && xb == 0) { // if the mantissa is zero...
|
|
this.A = this.B = 0; // the whole result is zero, and we're done
|
|
} else { // otherwise, determine the resulting sign
|
|
// Check for exponent over/underflow
|
|
if (eb > 63) {
|
|
eb %= 64;
|
|
if (this.NCSF) {
|
|
this.I = (this.I & 0x0F) | 0xB0; // set I05/6/8: exponent-overflow
|
|
this.cc.signalInterrupt();
|
|
}
|
|
} else if (eb < 0) {
|
|
if (eb >= -63) {
|
|
eb = (-eb) | 0x40; // set the exponent sign bit
|
|
} else {
|
|
eb = ((-eb) % 64) | 0x40; // mod the exponent and set its sign
|
|
if (this.NCSF) {
|
|
this.I = (this.I & 0x0F) | 0xA0;// set I06/8: exponent-underflow
|
|
this.cc.signalInterrupt();
|
|
}
|
|
}
|
|
}
|
|
|
|
this.X = xb; // for display purposes only
|
|
this.B = xb;
|
|
this.A = (sb*128 + eb)*0x8000000000 + mb; // Final Answer
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.computeRelativeAddr = function computeRelativeAddr(offset, cEnabled) {
|
|
/* Computes an absolute memory address from the relative "offset" parameter
|
|
and leaves it in the M register. See Table 6-1 in the B5500 Reference
|
|
Manual. "cEnable" determines whether C-relative addressing is permitted.
|
|
This offset must be in (0..1023) */
|
|
|
|
if (this.SALF) {
|
|
this.cycleCount += 2; // approximate the timing
|
|
switch ((offset % 0x400) >>> 7) {
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
this.M = this.R*64 + (offset % 0x200);
|
|
break;
|
|
case 4:
|
|
case 5:
|
|
if (this.MSFF) {
|
|
this.M = this.R*64 + 7;
|
|
this.loadMviaM(); // M = [M].[18:15]
|
|
this.M += (offset % 0x100);
|
|
} else {
|
|
this.M = this.F + (offset % 0x100);
|
|
}
|
|
break;
|
|
case 6:
|
|
if (cEnabled) {
|
|
this.M = (this.L ? this.C : this.C-1) + (offset % 0x80); // adjust C for fetch
|
|
} else {
|
|
this.M = this.R*64 + (offset % 0x80);
|
|
}
|
|
break;
|
|
case 7:
|
|
if (this.MSFF) {
|
|
this.M = this.R*64 + 7;
|
|
this.loadMviaM(); // M = [M].[18:15]
|
|
this.M -= (offset % 0x80);
|
|
} else {
|
|
this.M = this.F - (offset % 0x80);
|
|
}
|
|
break;
|
|
} // switch
|
|
} else {
|
|
this.M = this.R*64 + (offset % 0x400);
|
|
}
|
|
|
|
// Reset variant-mode R-relative addressing, if enabled
|
|
if (this.VARF) {
|
|
this.SALF = 1;
|
|
this.VARF = 0;
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.presenceTest = function presenceTest(word) {
|
|
/* Tests and returns the presence bit [2:1] of the "word" parameter,
|
|
which it assumes is a control word. If [2:1] is 0, the p-bit interrupt
|
|
is set; otherwise no further action */
|
|
|
|
if (word % 0x400000000000 >= 0x200000000000) {
|
|
return 1;
|
|
} else {
|
|
if (this.NCSF) {
|
|
this.I = (this.I & 0x0F) | 0x70; // set I05/6/7: p-bit
|
|
this.cc.signalInterrupt();
|
|
}
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.indexDescriptor = function indexDescriptor() {
|
|
/* Indexes a descriptor and, if successful leaves the indexed value in
|
|
the A register. Returns 1 if an interrupt is set and the syllable is
|
|
to be exited */
|
|
var aw = this.A; // local copy of A reg
|
|
var bw; // local copy of B reg
|
|
var interrupted = 0; // fatal error, interrupt set
|
|
var xe; // index exponent
|
|
var xm; // index mantissa
|
|
var xo; // last index octade shifted off
|
|
var xs; // index mantissa sign
|
|
var xt; // index exponent sign
|
|
|
|
this.adjustBFull();
|
|
bw = this.B;
|
|
xm = (bw % 0x8000000000);
|
|
xe = (bw - xm)/0x8000000000;
|
|
xs = (xe >>> 7) & 0x01;
|
|
xt = (xe >>> 6) & 0x01;
|
|
xe = (xt ? -(xe & 0x3F) : (xe & 0x3F));
|
|
|
|
// Normalize the index, if necessary
|
|
if (xe < 0) { // index exponent is negative
|
|
do {
|
|
this.cycleCount++;
|
|
xo = xm % 8;
|
|
xm = (xm - xo)/8;
|
|
} while (++xe < 0);
|
|
if (xo >= 4) {
|
|
xm++; // round the index
|
|
}
|
|
} else if (xe > 0) { // index exponent is positive
|
|
do {
|
|
this.cycleCount++;
|
|
if (xm < 0x1000000000) {
|
|
xm *= 8;
|
|
} else { // oops... integer overflow normalizing the index
|
|
xe = 0; // kill the loop
|
|
interrupted = 1;
|
|
if (this.NCSF) {
|
|
this.I = (this.I & 0x0F) | 0xC0; // set I07/8: integer overflow
|
|
this.cc.signalInterrupt();
|
|
}
|
|
}
|
|
} while (--xe > 0);
|
|
}
|
|
|
|
// Now we have an integerized index value in xm
|
|
if (!interrupted) {
|
|
if (xs && xm) { // oops... negative index
|
|
interrupted = 1;
|
|
if (this.NCSF) {
|
|
this.I = (this.I & 0x0F) | 0x90; // set I05/8: invalid-index
|
|
this.cc.signalInterrupt();
|
|
}
|
|
} else if (xm % 0x0400 >= (aw % 0x10000000000 - aw % 0x40000000)/0x40000000) {
|
|
interrupted = 1; // oops... index out of bounds
|
|
if (this.NCSF) {
|
|
this.I = (this.I & 0x0F) | 0x90; // set I05/8: invalid-index
|
|
this.cc.signalInterrupt();
|
|
}
|
|
} else { // we finally have a valid index
|
|
this.A = this.cc.fieldInsert(aw, 33, 15, aw % 0x8000 + xm % 0x400);
|
|
this.BROF = 0;
|
|
}
|
|
}
|
|
|
|
return interrupted;
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.integerStore = function integerStore(conditional, destructive) {
|
|
/* Store the value in the B register at the address in the A register (relative
|
|
or descriptor) and marks the A register empty. "conditional" indicates that
|
|
integerization is conditional on the type of word in A, and if a descriptor,
|
|
whether it has the integer bit set */
|
|
var aw; // local copy of A reg
|
|
var bw; // local copy of B reg
|
|
var be; // B exponent
|
|
var bm; // B mantissa
|
|
var bo; // last B octade shifted off
|
|
var bs; // B mantissa sign
|
|
var bt; // B exponent sign
|
|
var doStore = 1; // okay to store
|
|
var normalize = 1; // okay to integerize
|
|
|
|
this.adjustABFull();
|
|
aw = this.A;
|
|
if (aw < 0x800000000000) { // it's an operand
|
|
this.computeRelativeAddr(aw, 0);
|
|
} else { // it's a descriptor
|
|
if (this.presenceTest(aw)) {
|
|
this.M = aw % 0x8000;
|
|
if (conditional) {
|
|
if (aw % 0x20000000 < 0x10000000) { // [19:1] is the integer bit
|
|
normalize = 0;
|
|
}
|
|
}
|
|
} else {
|
|
doStore = normalize = 0;
|
|
}
|
|
}
|
|
|
|
if (normalize) {
|
|
bw = this.B;
|
|
bm = (bw % 0x8000000000);
|
|
be = (bw - bm)/0x8000000000;
|
|
bs = (be >>> 7) & 0x01;
|
|
bt = (be >>> 6) & 0x01;
|
|
be = (bt ? -(be & 0x3F) : (be & 0x3F));
|
|
|
|
if (be != 0) { // is B non-integer?
|
|
if (be < 0) { // B exponent is negative
|
|
do {
|
|
this.cycleCount++;
|
|
bo = bm % 8;
|
|
bm = (bm - bo)/8;
|
|
} while (++be < 0);
|
|
if (bs ? bo > 4 : bo >= 4) {
|
|
bm++; // round the B mantissa
|
|
}
|
|
} else { // B exponent is positive and not zero
|
|
do {
|
|
this.cycleCount++;
|
|
if (bm < 0x1000000000) {
|
|
bm *= 8;
|
|
} else { // oops... integer overflow normalizing the mantisa
|
|
doStore = 0;
|
|
if (this.NCSF) {
|
|
this.I = (this.I & 0x0F) | 0xC0; // set I07/8: integer overflow
|
|
this.cc.signalInterrupt();
|
|
}
|
|
break; // kill the loop
|
|
}
|
|
} while (--be > 0);
|
|
}
|
|
if (doStore) {
|
|
this.B = bs*0x400000000000 + bm;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (doStore) {
|
|
this.storeBviaM();
|
|
this.AROF = 0;
|
|
if (destructive) {
|
|
this.BROF = 0;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.buildMSCW = function buildMSCW() {
|
|
/* Return a Mark Stack Control Word from current processor state */
|
|
|
|
return this.F * 0x8000 +
|
|
this.SALF * 0x40000000 +
|
|
this.MSFF * 0x80000000 +
|
|
this.R * 0x200000000 +
|
|
0xC00000000000;
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.applyMSCW = function applyMSCW(word) {
|
|
/* Set processor state from fields of the Mark Stack Control
|
|
Word in the "word" parameter */
|
|
var f;
|
|
|
|
f = word % 0x8000; // [33:15], not used
|
|
word = (word-f)/0x8000;
|
|
this.F = f = word % 0x8000; // [18:15], F register
|
|
word = (word-f)/0x8000;
|
|
this.SALF = f = word % 2; // [17:1], SALF
|
|
word = (word-f)/2;
|
|
this.MSFF = word % 2; // [16:1], MSFF
|
|
word = (word - word%4)/4;
|
|
this.R = word % 0x200; // [6:9], R
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.buildRCW = function buildRCW(descriptorCall) {
|
|
/* Return a Return Control Word from the current processor state */
|
|
|
|
return this.C +
|
|
this.F * 0x8000 +
|
|
this.K * 0x40000000 +
|
|
this.G * 0x200000000 +
|
|
this.L * 0x1000000000 +
|
|
this.V * 0x4000000000 +
|
|
this.H * 0x20000000000 +
|
|
(descriptorCall ? 0xE00000000000 : 0xC00000000000);
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.applyRCW = function applyRCW(word, inline) {
|
|
/* Set processor state from fields of the Return Control Word in
|
|
the "word" parameter. If "inline" is truthy, C & L are NOT restored from
|
|
the RCW. Returns the state of the OPDC/DESC bit [2:1] */
|
|
var f;
|
|
|
|
f = word % 0x8000; // [33:15], C
|
|
if (!inline) {
|
|
this.C = f;
|
|
this.PROF = 0; // require fetch at SECL
|
|
}
|
|
word = (word-f)/0x8000;
|
|
this.F = f = word % 0x8000; // [18:15], F
|
|
word = (word-f)/0x8000;
|
|
this.K = f = word % 8; // [15:3], K
|
|
word = (word-f)/8;
|
|
this.G = f = word % 8; // [12:3], G
|
|
word = (word-f)/8;
|
|
f = word % 4; // [10:2], L
|
|
if (!inline) {
|
|
this.L = f;
|
|
}
|
|
word = (word-f)/4;
|
|
this.V = f = word % 8; // [7:3], V
|
|
word = (word-f)/8;
|
|
this.H = word % 8; // [4:3], H
|
|
word = (word - word % 16)/16;
|
|
return word % 2; // [2:1], DESC bit
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.enterCharModeInline = function enterCharModeInline() {
|
|
/* Implements the 4441=CMN syllable */
|
|
var bw; // local copy of B reg
|
|
|
|
this.adjustAEmpty(); // flush TOS registers, but tank TOS value in A
|
|
if (this.BROF) {
|
|
this.A = this.B; // tank the DI address in A
|
|
this.adjustBEmpty();
|
|
} else {
|
|
this.loadAviaS(); // A = [S]: load the DI address
|
|
this.AROF = 0;
|
|
}
|
|
this.B = this.buildRCW(0);
|
|
this.BROF = 1;
|
|
this.adjustBEmpty();
|
|
this.MSFF = 0;
|
|
this.SALF = 1;
|
|
this.F = this.S;
|
|
this.R = 0;
|
|
this.CWMF = 1;
|
|
this.X = this.S * 0x8000; // inserting S into X.[18:15], but X is zero at this point
|
|
this.V = 0;
|
|
this.B = bw = this.A;
|
|
|
|
// execute the portion of CM XX04=RDA operator starting at J=2
|
|
this.S = bw % 0x8000;
|
|
if (bw < 0x800000000000) { // if it's an operand
|
|
this.K = (bw % 0x40000) >>> 15; // set K from [30:3]
|
|
} else {
|
|
this.K = 0; // otherwise, force K to zero and
|
|
this.presenceTest(bw); // just take the side effect of any p-bit interrupt
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.enterSubroutine = function enterSubroutine(descriptorCall) {
|
|
/* Enters a subroutine via the present Program Descriptor in A as part
|
|
of an OPDC or DESC syllable. Also handles accidental entry */
|
|
var aw = this.A; // local copy of word in A reg
|
|
var arg = (aw % 0x100000000000 - aw % 0x40000000000)/0x40000000000; // aw.[4:2]
|
|
var mode = arg >>> 1; // descriptor mode bit (aw.[4:1], 1=char mode)
|
|
|
|
arg &= 0x01; // descriptor argument bit (aw.[5:1])
|
|
|
|
if (arg && !this.MSFF) {
|
|
// just leave the Program Descriptor on TOS
|
|
} else if (mode && !arg) {
|
|
// ditto
|
|
} else {
|
|
// Now we are really going to enter the subroutine
|
|
this.adjustBEmpty();
|
|
if (!arg) {
|
|
// Accidental entry -- mark the stack
|
|
this.B = this.buildMSCW();
|
|
this.BROF = 1;
|
|
this.adjustBEmpty();
|
|
this.F = this.S;
|
|
}
|
|
|
|
// Push a RCW
|
|
this.B = this.buildRCW(descriptorCall);
|
|
this.BROF = 1;
|
|
this.adjustBEmpty();
|
|
|
|
// Fetch the first word of subroutine code
|
|
this.C = aw % 0x8000;
|
|
this.L = 0;
|
|
this.PROF = 0; // require fetch at SECL
|
|
|
|
// Fix up the rest of the registers
|
|
if (arg) {
|
|
this.F = this.S;
|
|
} else {
|
|
this.F = (aw % 0x40000000) >>> 15; // aw.[18:15]
|
|
}
|
|
this.AROF = 0;
|
|
this.BROF = 0;
|
|
this.SALF = 1;
|
|
this.MSFF = 0;
|
|
if (mode) {
|
|
this.CWMF = 1;
|
|
this.R = 0;
|
|
this.X = this.cc.fieldInsert(this.X, 18, 15, this.S);
|
|
this.S = 0;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.exitSubroutine = function exitSubroutine(inline) {
|
|
/* Exits a subroutine by restoring the processor state from RCW and MSCW words
|
|
in the stack. "inline" indicates the C & L registers are NOT restored from the
|
|
RCW. The RCW is assumed to be in the B register, pointing to the MSCW.
|
|
The A register is not affected by this routine. If SALF & MSFF bits in the MSCW
|
|
are set, link back through the MSCWs until one is found that has either bit not
|
|
set, and store that MSCW at [R]+7. This is the last prior MSCW that actually
|
|
points to a RCW, thus skipping over any pending subroutine calls that are still
|
|
building their parameters in the stack. Returns results as follows:
|
|
0 = entered by OPDC
|
|
1 = entered by DESC
|
|
2 = flag bit interrupt set, terminate operator
|
|
*/
|
|
var result;
|
|
|
|
if (this.B < 0x800000000000) { // flag bit not set
|
|
result = 2;
|
|
if (this.NCSF) {
|
|
this.I = (this.I & 0x0F) | 0x80; // set I08: flag-bit
|
|
this.cc.signalInterrupt();
|
|
}
|
|
} else { // flag bit is set
|
|
result = this.applyRCW(this.B, inline);
|
|
this.X = this.B % 0x8000000000; // save F setting from RCW to restore S at end
|
|
|
|
this.S = this.F;
|
|
this.loadBviaS(); // B = [S], fetch the MSCW
|
|
this.applyMSCW(this.B);
|
|
|
|
if (this.MSFF && this.SALF) {
|
|
this.Q |= 0x20; // set Q06F, not used except for display
|
|
do {
|
|
this.S = (this.B % 0x40000000) >>> 15;
|
|
this.loadBviaS(); // B = [S], fetch prior MSCW
|
|
} while ((this.B % 0x100000000 - this.B % 0x80000000)/0x80000000); // MSFF
|
|
this.S = this.R*64 + 7;
|
|
this.storeBviaS(); // [S] = B, store last MSCW at [R]+7
|
|
}
|
|
this.S = ((this.X % 0x40000000) >>> 15) - 1;
|
|
this.BROF = 0;
|
|
}
|
|
return result;
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.operandCall = function operandCall() {
|
|
/* OPDC, the moral equivalent of "load accumulator" on lesser
|
|
machines. Assumes the syllable has already loaded a word into A.
|
|
See Figures 6-1, 6-3, and 6-4 in the B5500 Reference Manual */
|
|
var aw = this.A; // local copy of A reg value
|
|
var interrupted = 0; // interrupt occurred
|
|
|
|
if (aw >= 0x800000000000) {
|
|
// It's not a simple operand
|
|
switch ((aw % 0x800000000000 - aw % 0x100000000000)/0x100000000000) { // aw.[1:3]
|
|
case 2:
|
|
case 3:
|
|
// Present data descriptor
|
|
if ((aw % 0x10000000000 - aw % 0x40000000)/0x40000000) { // aw.[8:10]
|
|
interrupted = this.indexDescriptor();
|
|
// else descriptor is already indexed (word count 0)
|
|
}
|
|
if (!interrupted) {
|
|
this.M = this.A % 0x8000;
|
|
this.loadAviaM(); // A = [M]
|
|
if (this.A >= 0x800000000000 && this.NCSF) {
|
|
// Flag bit is set
|
|
this.I = (this.I & 0x0F) | 0x80; // set I08: flag-bit interrupt
|
|
this.cc.signalInterrupt();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 7:
|
|
// Present program descriptor
|
|
this.enterSubroutine(0);
|
|
break;
|
|
|
|
case 0:
|
|
case 1:
|
|
case 5:
|
|
// Absent data or program descriptor
|
|
if (this.NCSF) {
|
|
this.I = (this.I & 0x0F) | 0x70; // set I05/6/7: p-bit
|
|
this.cc.signalInterrupt();
|
|
// else if control state, we're done
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Miscellaneous control word -- leave as is
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.descriptorCall = function descriptorCall() {
|
|
/* DESC, the moral equivalent of "load address" on lesser machines.
|
|
Assumes the syllable has already loaded a word into A, and that the
|
|
address of that word is in M.
|
|
See Figures 6-2, 6-3, and 6-4 in the B5500 Reference Manual */
|
|
var aw = this.A; // local copy of A reg value
|
|
var interrupted = 0; // interrupt occurred
|
|
|
|
if (aw < 0x800000000000) {
|
|
// It's a simple operand
|
|
this.A = this.M + 0xA00000000000;
|
|
} else {
|
|
// It's not a simple operand
|
|
switch ((aw % 0x800000000000 - aw % 0x100000000000)/0x100000000000) { // aw.[1:3]
|
|
case 2:
|
|
case 3:
|
|
// Present data descriptor
|
|
if ((aw % 0x10000000000 - aw % 0x40000000)/0x40000000) { // aw.[8:10]
|
|
interrupted = this.indexDescriptor();
|
|
this.A = this.cc.fieldInsert(this.A, 8, 10, 0); // set word count to zero
|
|
// else descriptor is already indexed (word count 0)
|
|
}
|
|
break;
|
|
|
|
case 7:
|
|
// Present program descriptor
|
|
this.enterSubroutine(1);
|
|
break;
|
|
|
|
case 0:
|
|
case 1:
|
|
case 5:
|
|
// Absent data or program descriptor
|
|
if (this.NCSF) {
|
|
this.I = (this.I & 0x0F) | 0x70; // set I05/6/7: p-bit
|
|
this.cc.signalInterrupt();
|
|
// else if control state, we're done
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Miscellaneous control word
|
|
this.A = this.M + 0xA00000000000;
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.run = function run() {
|
|
/* 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 -- 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 */
|
|
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
|
|
var t2; // ditto
|
|
var t3; // ditto
|
|
var t4; // ditto
|
|
var variant; // high-order six bits of T register
|
|
|
|
do {
|
|
this.Q = 0;
|
|
this.Y = 0;
|
|
this.Z = 0;
|
|
opcode = this.T;
|
|
this.cycleCount = 1; // general syllable execution overhead
|
|
|
|
if (this.CWMF) {
|
|
/***********************************************************
|
|
* Character Mode Syllables *
|
|
***********************************************************/
|
|
do { // inner loop to support CRF dynamic repeat count
|
|
variant = opcode >>> 6;
|
|
noSECL = 0; // force off by default (set by CRF)
|
|
switch (opcode & 0x3F) {
|
|
case 0x00: // XX00: CMX, EXC: Exit character mode
|
|
if (this.BROF) {
|
|
this.storeBviaS(); // store destination string
|
|
}
|
|
this.S = this.F;
|
|
this.loadBviaS(); // B = [S], fetch the RCW
|
|
this.exitSubroutine(variant & 0x01);// 0=exit, 1=exit inline
|
|
this.AROF = this.BROF = 0;
|
|
this.X = this.M = this.N = 0;
|
|
this.CWMF = 0;
|
|
break;
|
|
|
|
case 0x02: // XX02: BSD=Skip bit destination
|
|
this.cycleCount += variant;
|
|
t1 = this.K*6 + this.V + variant;
|
|
while (t1 >= 48) {
|
|
if (this.BROF) { // skipped off initial word, so
|
|
this.storeBviaS(); // [S] = B
|
|
this.BROF = 0; // invalidate B
|
|
}
|
|
this.S++;
|
|
t1 -= 48;
|
|
}
|
|
this.K = (t1 - (this.V = t1 % 6))/6;
|
|
break;
|
|
|
|
case 0x03: // XX03: BSS=Skip bit source
|
|
this.cycleCount += variant;
|
|
t1 = this.G*6 + this.H + variant;
|
|
while (t1 >= 48) {
|
|
this.M++; // skipped off initial word, so
|
|
this.AROF = 0; // invalidate A
|
|
t1 -= 48;
|
|
}
|
|
this.G = (t1 - (this.H = t1 % 6))/6;
|
|
break;
|
|
|
|
case 0x04: // XX04: RDA=Recall destination address
|
|
this.cycleCount += variant;
|
|
if (this.BROF) {
|
|
this.storeBviaS(); // [S] = B
|
|
this.BROF = 0;
|
|
}
|
|
this.V = 0;
|
|
this.S = this.F - variant;
|
|
this.loadBviaS(); // B = [S]
|
|
this.BROF = 0;
|
|
this.S = (t1 = this.B) % 0x8000;
|
|
if (t1 < 0x800000000000) { // if it's an operand,
|
|
this.K = (t1 % 0x40000) >>> 15;// set K from [30:3]
|
|
} else {
|
|
this.K = 0; // otherwise, force K to zero and
|
|
this.presenceTest(t1); // just take the side effect of any p-bit interrupt
|
|
}
|
|
break;
|
|
|
|
case 0x05: // XX05: TRW=Transfer words
|
|
if (this.BROF) {
|
|
this.storeBviaS(); // [S] = B
|
|
this.BROF = 0;
|
|
}
|
|
if (this.G || this.H) {
|
|
this.G = this.H = 0;
|
|
this.M++;
|
|
this.AROF = 0;
|
|
}
|
|
if (this.K || this.V) {
|
|
this.K = this.V = 0;
|
|
this.S++;
|
|
}
|
|
if (variant) { // count > 0
|
|
if (!this.AROF) {
|
|
this.loadAviaM(); // A = [M]
|
|
}
|
|
do {
|
|
this.storeAviaS(); // [S] = A
|
|
this.S++;
|
|
this.M++;
|
|
this.loadAviaM(); // A = [M]
|
|
} while (--variant);
|
|
}
|
|
break;
|
|
|
|
case 0x06: // XX06: SED=Set destination address
|
|
this.cycleCount += variant;
|
|
if (this.BROF) {
|
|
this.storeBviaS(); // [S] = B
|
|
this.BROF = 0;
|
|
}
|
|
this.S = this.F - variant;
|
|
this.K = this.V = 0;
|
|
break;
|
|
|
|
case 0x07: // XX07: TDA=Transfer destination address
|
|
this.cycleCount += 6;
|
|
this.streamAdjustDestChar();
|
|
if (this.BROF) {
|
|
this.storeBviaS(); // [S] = B, store B at dest addresss
|
|
}
|
|
t1 = this.M; // save M (not the way the hardware did it)
|
|
t2 = this.G; // save G (ditto)
|
|
this.M = this.S; // copy dest address to source address
|
|
this.G = this.K;
|
|
this.A = this.B; // save B
|
|
this.AROF = this.BROF;
|
|
if (!this.AROF) {
|
|
this.loadAviaM(); // A = [M], load A from source address
|
|
}
|
|
for (variant=3; variant>0; variant--) {
|
|
this.B = (this.B % 0x40000000000)*0x40 +
|
|
(this.Y = this.cc.fieldIsolate(this.A, this.G*6, 6));
|
|
if (this.G < 7) {
|
|
this.G++;
|
|
} else {
|
|
this.G = 0;
|
|
this.M++;
|
|
this.loadAviaM(); // A = [M]
|
|
}
|
|
}
|
|
this.S = this.B % 0x8000;
|
|
this.K = (this.B % 0x40000) >>> 15;
|
|
this.M = t1; // restore M & G
|
|
this.G = t2;
|
|
this.AROF = this.BROF = 0; // invalidate A & B
|
|
break;
|
|
|
|
case 0x09: // XX11: control state ops
|
|
switch (variant) {
|
|
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);
|
|
break;
|
|
|
|
case 0x1C: // 3411: SFT=Store for Test
|
|
this.storeForInterrupt(1);
|
|
break;
|
|
|
|
default: // Anything else is a no-op
|
|
break;
|
|
} // end switch for XX11 ops
|
|
break;
|
|
|
|
case 0x0A: // XX12: TBN=Transfer blank for numeric
|
|
this.MSFF = 1; // initialize true-false FF
|
|
this.streamToDest(variant, function TBN(bb, count) {
|
|
var c = this.Z = this.cc.fieldIsolate(this.B, bb, 6);
|
|
var result = 0;
|
|
|
|
if (c > 0 && c <= 9) {
|
|
this.MSFF = 0; // numeric, non-zero: stop blanking
|
|
this.Q |= 0x04; // set Q03F (display only)
|
|
result = 1; // terminate, pointing at this char
|
|
} else {
|
|
this.B = this.cc.fieldInsert(this.B, bb, 6, 0x30); // replace with blank
|
|
}
|
|
return result;
|
|
});
|
|
break;
|
|
|
|
case 0x0C: // XX14: SDA=Store destination address
|
|
this.cycleCount += variant;
|
|
this.streamAdjustDestChar();
|
|
this.A = this.B; // save B
|
|
this.AROF = this.BROF;
|
|
this.B = this.K*0x8000 + this.S;
|
|
t1 = this.S; // save S (not the way the hardware did it)
|
|
this.S = this.F - variant;
|
|
this.storeBviaS(); // [S] = B
|
|
this.S = t1; // restore S
|
|
this.B = this.A; // restore B from A
|
|
this.BROF = this.AROF;
|
|
this.AROF = 0; // invalidate A
|
|
break;
|
|
|
|
case 0x0D: // XX15: SSA=Store source address
|
|
this.cycleCount += variant;
|
|
this.streamAdjustSourceChar();
|
|
this.A = this.B; // save B
|
|
this.AROF = this.BROF;
|
|
this.B = this.G*0x8000 + this.M;
|
|
t1 = this.M; // save M (not the way the hardware did it)
|
|
this.M = this.F - variant;
|
|
this.storeBviaM(); // [M] = B
|
|
this.M = t1; // restore M
|
|
this.B = this.A; // restore B from A
|
|
this.BROF = this.AROF;
|
|
this.AROF = 0; // invalidate A
|
|
break;
|
|
|
|
case 0x0E: // XX16: SFD=Skip forward destination
|
|
this.cycleCount += (variant >>> 3) + (variant & 0x07);
|
|
this.streamAdjustDestChar();
|
|
if (this.BROF && this.K + variant >= 8) {
|
|
this.storeBviaS(); // will skip off the current word,
|
|
this.BROF = 0; // so store and invalidate B
|
|
}
|
|
t1 = this.S*8 + this.K + variant;
|
|
this.S = t1 >>> 3;
|
|
this.K = t1 & 0x07;
|
|
break;
|
|
|
|
case 0x0F: // XX17: SRD=Skip reverse destination
|
|
this.cycleCount += (variant >>> 3) + (variant & 0x07);
|
|
this.streamAdjustDestChar();
|
|
if (this.BROF && this.K < variant) {
|
|
this.storeBviaS(); // will skip off the current word,
|
|
this.BROF = 0; // so store and invalidate B
|
|
}
|
|
t1 = this.S*8 + this.K - variant;
|
|
this.S = t1 >>> 3;
|
|
this.K = t1 & 0x07;
|
|
break;
|
|
|
|
case 0x12: // XX22: SES=Set source address
|
|
this.cycleCount += variant;
|
|
this.M = this.F - variant;
|
|
this.G = this.H = 0;
|
|
this.AROF = 0;
|
|
break;
|
|
|
|
case 0x14: // XX24: TEQ=Test for equal
|
|
this.streamAdjustSourceChar();
|
|
if (!this.AROF) {
|
|
this.loadAviaM(); // A = [M]
|
|
}
|
|
t1 = this.cc.fieldIsolate(this.A, this.G*6, 6);
|
|
this.MSFF = (t1 == variant ? 1 : 0);
|
|
break;
|
|
|
|
case 0x15: // XX25: TNE=Test for not equal
|
|
this.streamAdjustSourceChar();
|
|
if (!this.AROF) {
|
|
this.loadAviaM(); // A = [M]
|
|
}
|
|
t1 = this.cc.fieldIsolate(this.A, this.G*6, 6);
|
|
this.MSFF = (t1 != variant ? 1 : 0);
|
|
break;
|
|
|
|
case 0x16: // XX26: TEG=Test for equal or greater
|
|
this.streamAdjustSourceChar();
|
|
if (!this.AROF) {
|
|
this.loadAviaM(); // A = [M]
|
|
}
|
|
t1 = B5500Processor.collation[this.cc.fieldIsolate(this.A, this.G*6, 6)];
|
|
t2 = B5500Processor.collation[variant];
|
|
this.MSFF = (t1 >= t2 ? 1 : 0);
|
|
break;
|
|
|
|
case 0x17: // XX27: TGR=Test for greater
|
|
this.streamAdjustSourceChar();
|
|
if (!this.AROF) {
|
|
this.loadAviaM(); // A = [M]
|
|
}
|
|
t1 = B5500Processor.collation[this.cc.fieldIsolate(this.A, this.G*6, 6)];
|
|
t2 = B5500Processor.collation[variant];
|
|
this.MSFF = (t1 > t2 ? 1 : 0);
|
|
break;
|
|
|
|
case 0x18: // XX30: SRS=Skip reverse source
|
|
this.cycleCount += (variant >>> 3) + (variant & 0x07);
|
|
this.streamAdjustSourceChar();
|
|
if (this.G < variant) {
|
|
this.AROF = 0; // will skip off the current word
|
|
}
|
|
t1 = this.M*8 + this.G - variant;
|
|
this.M = t1 >>> 3;
|
|
this.G = t1 & 0x07;
|
|
break;
|
|
|
|
case 0x19: // XX31: SFS=Skip forward source
|
|
this.cycleCount += (variant >>> 3) + (variant & 0x07);
|
|
this.streamAdjustSourceChar();
|
|
if (this.G + variant >= 8) { // will skip off the current word
|
|
this.AROF = 0;
|
|
}
|
|
t1 = this.M*8 + this.G + variant;
|
|
this.G = t1 & 0x07;
|
|
this.M = t1 >>> 3;
|
|
break;
|
|
|
|
case 0x1A: // XX32: xxx=Field subtract (aux)
|
|
this.fieldArithmetic(variant, false);
|
|
break;
|
|
|
|
case 0x1B: // XX33: xxx=Field add (aux)
|
|
this.fieldArithmetic(variant, true);
|
|
break;
|
|
|
|
case 0x1C: // XX34: TEL=Test for equal or less
|
|
this.streamAdjustSourceChar();
|
|
if (!this.AROF) {
|
|
this.loadAviaM(); // A = [M]
|
|
}
|
|
t1 = B5500Processor.collation[this.cc.fieldIsolate(this.A, this.G*6, 6)];
|
|
t2 = B5500Processor.collation[variant];
|
|
this.MSFF = (t1 <= t2 ? 1 : 0);
|
|
break;
|
|
|
|
case 0x1D: // XX35: TLS=Test for less
|
|
this.streamAdjustSourceChar();
|
|
if (!this.AROF) {
|
|
this.loadAviaM(); // A = [M]
|
|
}
|
|
t1 = B5500Processor.collation[this.cc.fieldIsolate(this.A, this.G*6, 6)];
|
|
t2 = B5500Processor.collation[variant];
|
|
this.MSFF = (t1 < t2 ? 1 : 0);
|
|
break;
|
|
|
|
case 0x1E: // XX36: TAN=Test for alphanumeric
|
|
this.streamAdjustSourceChar();
|
|
if (!this.AROF) {
|
|
this.loadAviaM(); // A = [M]
|
|
}
|
|
this.Y = t1 = this.cc.fieldIsolate(this.A, this.G*6, 6);
|
|
this.Z = variant; // for display only
|
|
if (B5500Processor.collation[t1] > B5500Processor.collation[variant]) {
|
|
this.MSFF = (t1 == 0x20 ? 0 : (t1 == 0x3C ? 0 : 1)); // alphanumeric unless | or !
|
|
} else { // alphanumeric if equal
|
|
this.Q |= 0x04; // set Q03F (display only)
|
|
this.MSFF = (t1 == variant ? 1 : 0);
|
|
}
|
|
break;
|
|
|
|
case 0x1F: // XX37: BIT=Test bit
|
|
if (!this.AROF) {
|
|
this.loadAviaM(); // A = [M]
|
|
}
|
|
t1 = (this.Y = this.cc.fieldIsolate(this.A, this.G*6, 6)) >>> (5-this.H);
|
|
this.MSFF = ((t1 & 0x01) == (variant & 0x01) ? 1 : 0);
|
|
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
|
|
this.cycleCount += variant;
|
|
this.A = this.B; // save B
|
|
this.AROF = 0; // invalidate A
|
|
this.B = this.F; // save RCW address in B (why??)
|
|
if (this.BROF) {
|
|
this.storeAviaS(); // [S] = A, save original B contents
|
|
this.BROF = 0;
|
|
}
|
|
this.A = this.B; // move saved F address to A (why??)
|
|
this.B = this.R; // copy the TALLY value to B
|
|
t1 = this.S; // save S (not the way the hardware did it)
|
|
this.S = this.F - variant;
|
|
this.storeBviaS(); // [S] = B, store the TALLY value
|
|
this.B = this.A; // restore F address from A (why??)
|
|
this.S = t1; // restore S
|
|
this.BROF = 0; // invalidate B
|
|
break;
|
|
|
|
case 0x22: // XX42: SEC=Set TALLY
|
|
this.R = variant;
|
|
break;
|
|
|
|
case 0x23: // XX43: CRF=Call repeat field
|
|
this.cycleCount += variant;
|
|
this.A = this.B; // save B in A
|
|
this.AROF = this.BROF;
|
|
t1 = this.S; // save S (not the way the hardware did it)
|
|
this.S = this.F - variant; // compute parameter address
|
|
this.loadBviaS(); // B = [S]
|
|
variant = this.B % 0x40; // dynamic repeat count is low-order 6 bits
|
|
this.S = t1; // restore S
|
|
this.B = this.A; // restore B from A
|
|
this.BROF = this.AROF;
|
|
this.AROF = 0; // invalidate A
|
|
if (!this.PROF) {
|
|
this.loadPviaC(); // fetch the program word, if necessary
|
|
}
|
|
opcode = this.cc.fieldIsolate(this.P, this.L*12, 12);
|
|
if (variant) {
|
|
// if repeat count from parameter > 0, apply it to the next syllable
|
|
this.T = opcode = (opcode & 0x3F) + variant*0x40;
|
|
} else {
|
|
// otherwise, construct JFW (XX47) using repeat count from next syl (whew!)
|
|
this.T = opcode = (opcode & 0xFC0) + 0x27;
|
|
}
|
|
|
|
// Since we are bypassing normal SECL behavior, bump the instruction pointer here.
|
|
noSECL = 1; // >>> override normal instruction fetch <<<
|
|
this.PROF = 0;
|
|
if (this.L < 3) {
|
|
this.L++;
|
|
} else {
|
|
this.L = 0;
|
|
this.C++;
|
|
}
|
|
break;
|
|
|
|
case 0x24: // XX44: JNC=Jump out of loop conditional
|
|
if (!this.MSFF) {
|
|
this.jumpOutOfLoop(variant);
|
|
}
|
|
break;
|
|
|
|
case 0x25: // XX45: JFC=Jump forward conditional
|
|
if (!this.MSFF) { // conditional on TFFF
|
|
this.cycleCount += (variant >>> 2) + (variant & 0x03);
|
|
this.jumpSyllables(variant);
|
|
}
|
|
break;
|
|
|
|
case 0x26: // XX46: JNS=Jump out of loop
|
|
this.jumpOutOfLoop(variant);
|
|
break;
|
|
|
|
case 0x27: // XX47: JFW=Jump forward unconditional
|
|
this.cycleCount += (variant >>> 2) + (variant & 0x03);
|
|
this.jumpSyllables(variant);
|
|
break;
|
|
|
|
case 0x28: // XX50: RCA=Recall control address
|
|
this.cycleCount += variant;
|
|
this.A = this.B; // save B in A
|
|
this.AROF = this.BROF;
|
|
t1 = this.S; // save S (not the way the hardware did it)
|
|
this.S = this.F - variant;
|
|
this.loadBviaS(); // B = [S]
|
|
this.S = t1;
|
|
t2 = this.B;
|
|
if (t2 >= 0x800000000000) { // if it's a descriptor,
|
|
if (this.presenceTest(t2)) { // if present, initiate a fetch to P
|
|
this.C = this.B % 0x8000; // get the word address,
|
|
this.L = 0; // force L to zero and
|
|
this.PROF = 0; // require fetch at SECL
|
|
}
|
|
} else {
|
|
this.C = t2 % 0x8000;
|
|
t1 = (t2 % 0x4000000000 - t2 % 0x1000000000)/0x1000000000;
|
|
if (t1 < 3) { // if not a descriptor, increment the address
|
|
this.L = t1+1;
|
|
} else {
|
|
this.L = 0;
|
|
this.C++;
|
|
}
|
|
this.PROF = 0; // require fetch at SECL
|
|
}
|
|
this.B = this.A; // restore B
|
|
this.BROF = this.AROF;
|
|
this.AROF = 0; // invalidate A
|
|
break;
|
|
|
|
case 0x29: // XX51: ENS=End loop
|
|
this.cycleCount += 4;
|
|
this.A = this.B; // save B in A
|
|
this.AROF = this.BROF;
|
|
t1 = this.X;
|
|
variant = this.cc.fieldIsolate(t1, 12, 6); // get repeat count
|
|
if (variant) { // loop count exhausted?
|
|
this.C = this.cc.fieldIsolate(t1, 33, 15); // no, restore C, L, and P to loop again
|
|
this.L = this.cc.fieldIsolate(t1, 10, 2);
|
|
this.PROF = 0; // require fetch at SECL
|
|
this.X = this.cc.fieldInsert(t1, 12, 6, variant-1); // store decremented count in X
|
|
} else {
|
|
t2 = this.S; // save S (not the way the hardware did it)
|
|
this.S = this.cc.fieldIsolate(t1, 18, 15); // get prior LCW addr from X value
|
|
this.loadBviaS(); // B = [S], fetch prior LCW from stack
|
|
this.S = t2; // restore S
|
|
this.X = this.cc.fieldIsolate(this.B, 9, 39); // store prior LCW (less control bits) in X
|
|
}
|
|
this.B = this.A; // restore B
|
|
this.BROF = this.AROF;
|
|
this.AROF = 0; // invalidate A
|
|
break;
|
|
|
|
case 0x2A: // XX52: BNS=Begin loop
|
|
this.cycleCount += 4;
|
|
this.A = this.B; // save B in A (note that BROF is not altered)
|
|
t1 = this.cc.fieldInsert( // construct new LCW: insert repeat count
|
|
this.cc.fieldInsert( // insert L
|
|
this.cc.fieldInsert(this.X, 33, 15, this.C), // insert C
|
|
10, 2, this.L),
|
|
12, 6, (variant ? variant-1 : 0)); // decrement count for first iteration
|
|
this.B = this.cc.fieldInsert(this.X, 0, 2, 3); // set control bits [0:2]=3
|
|
t2 = this.S; // save S (not the way the hardware did it)
|
|
this.S = this.cc.fieldIsolate(t1, 18, 15)+1; // get F value from X value and ++
|
|
this.storeBviaS(); // [S] = B, save prior LCW in stack
|
|
this.X = this.cc.fieldInsert(t1, 18, 15, this.S); // update F value in X
|
|
this.S = t2; // restore S
|
|
this.B = this.A; // restore B (note that BROF is still relevant)
|
|
this.AROF = 0; // invalidate A
|
|
break;
|
|
|
|
case 0x2B: // XX53: RSA=Recall source address
|
|
this.cycleCount += variant;
|
|
this.A = this.B; // save B
|
|
this.AROF = this.BROF;
|
|
this.H = 0;
|
|
this.M = this.F - variant;
|
|
this.loadBviaM(); // B = [M]
|
|
t1 = this.B;
|
|
this.M = t1 % 0x8000;
|
|
if (t1 < 0x800000000000) { // if it's an operand,
|
|
this.G = (t1 % 0x40000) >>> 15; // set G from [30:3]
|
|
} else { //
|
|
this.G = 0; // otherwise, force G to zero and
|
|
this.presenceTest(t1); // just take the side effect of any p-bit interrupt
|
|
}
|
|
this.B = this.A; // restore B from A
|
|
this.BROF = this.AROF;
|
|
this.AROF = 0; // invalidate A
|
|
break;
|
|
|
|
case 0x2C: // XX54: SCA=Store control address
|
|
this.cycleCount += variant;
|
|
this.A = this.B; // save B
|
|
this.AROF = this.BROF;
|
|
t2 = this.S; // save S (not the way the hardware did it)
|
|
this.S = this.F - variant; // compute store address
|
|
this.B = this.C +
|
|
this.F * 0x8000 +
|
|
this.L * 0x1000000000;
|
|
this.storeBviaS(); // [S] = B
|
|
this.S = t2; // restore S
|
|
this.B = this.A; // restore B from A
|
|
this.BROF = this.AROF;
|
|
this.AROF = 0; // invalidate A
|
|
break;
|
|
|
|
case 0x2D: // XX55: JRC=Jump reverse conditional
|
|
if (!this.MSFF) { // conditional on TFFF
|
|
this.cycleCount += (variant >>> 2) + (variant & 0x03);
|
|
this.jumpSyllables(-variant);
|
|
}
|
|
break;
|
|
|
|
case 0x2E: // XX56: TSA=Transfer source address
|
|
this.streamAdjustSourceChar();
|
|
if (this.BROF) {
|
|
this.storeBviaS(); // [S] = B, store B at dest addresss
|
|
this.BROF = 0;
|
|
}
|
|
if (!this.AROF) {
|
|
this.loadAviaM(); // A = [M], load A from source address
|
|
}
|
|
for (variant=3; variant>0; variant--) {
|
|
this.B = (this.B % 0x40000000000)*0x40 +
|
|
(this.Y = this.cc.fieldIsolate(this.A, this.G*6, 6));
|
|
if (this.G < 7) {
|
|
this.G++;
|
|
} else {
|
|
this.G = 0;
|
|
this.M++;
|
|
this.loadAviaM(); // A = [M]
|
|
}
|
|
}
|
|
this.M = this.B % 0x8000;
|
|
this.G = (this.B % 0x40000) >>> 15;
|
|
this.AROF = 0; // invalidate A
|
|
break;
|
|
|
|
case 0x2F: // XX57: JRV=Jump reverse unconditional
|
|
this.cycleCount += (variant >>> 2) + (variant & 0x03);
|
|
this.jumpSyllables(-variant);
|
|
break;
|
|
|
|
case 0x30: // XX60: CEQ=Compare equal
|
|
this.compareSourceWithDest(variant);
|
|
this.H = this.V = 0;
|
|
this.MSFF = (this.Q & 0x04 ? 0 : 1); // if !Q03F, S=D
|
|
break;
|
|
|
|
case 0x31: // XX61: CNE=Compare not equal
|
|
this.compareSourceWithDest(variant);
|
|
this.H = this.V = 0;
|
|
this.MSFF = (this.Q & 0x04 ? 1 : 0); // if Q03F, S!=D
|
|
break;
|
|
|
|
case 0x32: // XX62: CEG=Compare greater or equal
|
|
this.compareSourceWithDest(variant);
|
|
this.H = this.V = 0;
|
|
this.MSFF = (this.Q & 0x04 ? this.MSFF : 1); // if Q03F&MSFF, S>D; if !Q03F, S=D
|
|
break;
|
|
|
|
case 0x33: // XX63: CGR=Compare greater
|
|
this.compareSourceWithDest(variant);
|
|
this.H = this.V = 0;
|
|
this.MSFF = (this.Q & 0x04 ? this.MSFF : 0); // if Q03F&MSFF, S>D
|
|
break;
|
|
|
|
case 0x34: // XX64: BIS=Set bit
|
|
this.streamBitsToDest(variant, 0xFFFFFFFFFFFF);
|
|
break;
|
|
|
|
case 0x35: // XX65: BIR=Reset bit
|
|
this.streamBitsToDest(variant, 0);
|
|
break;
|
|
|
|
case 0x36: // XX66: OCV=Output convert
|
|
this.streamOutputConvert(variant);
|
|
break;
|
|
|
|
case 0x37: // XX67: ICV=Input convert
|
|
this.streamInputConvert(variant);
|
|
break;
|
|
|
|
case 0x38: // XX70: CEL=Compare equal or less
|
|
this.compareSourceWithDest(variant);
|
|
this.H = this.V = 0;
|
|
this.MSFF = (this.Q & 0x04 ? 1-this.MSFF : 1); // if Q03F&!MSFF, S<D; if !Q03F, S=D
|
|
break;
|
|
|
|
case 0x39: // XX71: CLS=Compare less
|
|
this.compareSourceWithDest(variant);
|
|
this.H = this.V = 0;
|
|
this.MSFF = (this.Q & 0x04 ? 1-this.MSFF : 0); // if Q03F&!MSFF, S<D
|
|
break;
|
|
|
|
case 0x3A: // XX72: FSU=Field subtract
|
|
this.fieldArithmetic(variant, false);
|
|
break;
|
|
|
|
case 0x3B: // XX73: FAD=Field add
|
|
this.fieldArithmetic(variant, true);
|
|
break;
|
|
|
|
case 0x3C: // XX74: TRP=Transfer program characters
|
|
this.streamProgramToDest(variant);
|
|
break;
|
|
|
|
case 0x3D: // XX75: TRN=Transfer source numerics
|
|
this.MSFF = 0; // initialize true-false FF
|
|
this.streamSourceToDest(variant, function TRN(bb, c, count) {
|
|
|
|
if (count == 1 && (c & 0x30) == 0x20) {
|
|
this.MSFF = 1; // neg. sign
|
|
}
|
|
this.B = this.cc.fieldInsert(this.B, bb, 6, c & 0x0F);
|
|
});
|
|
break;
|
|
|
|
case 0x3E: // XX76: TRZ=Transfer source zones
|
|
this.streamSourceToDest(variant, function TRZ(bb, c, count) {
|
|
this.B = this.cc.fieldInsert(this.B, bb, 2, c >>> 4);
|
|
});
|
|
break;
|
|
|
|
case 0x3F: // XX77: TRS=Transfer source characters
|
|
this.streamSourceToDest(variant, function TRS(bb, c, count) {
|
|
this.B = this.cc.fieldInsert(this.B, bb, 6, c);
|
|
});
|
|
break;
|
|
|
|
default: // everything else is a no-op
|
|
break;
|
|
} // end switch for character mode operators
|
|
} while (noSECL);
|
|
|
|
} else {
|
|
|
|
/***********************************************************
|
|
* Word Mode Syllables *
|
|
***********************************************************/
|
|
this.M = 0;
|
|
this.N = 0;
|
|
this.X = 0;
|
|
switch (opcode & 0x03) {
|
|
case 0: // LITC: Literal Call
|
|
this.adjustAEmpty();
|
|
this.A = opcode >>> 2;
|
|
this.AROF = 1;
|
|
break;
|
|
|
|
case 2: // OPDC: Operand Call
|
|
this.adjustAEmpty();
|
|
this.computeRelativeAddr(opcode >>> 2, 1);
|
|
this.loadAviaM();
|
|
if (this.A >= 0x800000000000) { // if it's a control word,
|
|
this.operandCall(); // evaluate it
|
|
} // otherwise, just leave it in A
|
|
break;
|
|
|
|
case 3: // DESC: Descriptor (name) Call
|
|
this.adjustAEmpty();
|
|
this.computeRelativeAddr(opcode >>> 2, 1);
|
|
this.loadAviaM();
|
|
this.descriptorCall();
|
|
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
|
|
this.singlePrecisionAdd(true);
|
|
break;
|
|
|
|
case 0x03: // 0301: SUB=single-precision subtract
|
|
this.singlePrecisionAdd(false);
|
|
break;
|
|
|
|
case 0x04: // 0401: MUL=single-precision multiply
|
|
this.singlePrecisionMultiply();
|
|
break;
|
|
|
|
case 0x08: // 1001: DIV=single-precision floating divide
|
|
this.singlePrecisionDivide();
|
|
break;
|
|
|
|
case 0x18: // 3001: IDV=integer divide
|
|
this.integerDivide();
|
|
break;
|
|
|
|
case 0x38: // 7001: RDV=remainder divide
|
|
this.remainderDivide();
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x05: // XX05: double-precision numerics
|
|
switch (variant) {
|
|
case 0x01: // 0105: DLA=double-precision add
|
|
this.doublePrecisionAdd(true);
|
|
break;
|
|
|
|
case 0x03: // 0305: DLS=double-precision subtract
|
|
this.doublePrecisionAdd(false);
|
|
break;
|
|
|
|
case 0x04: // 0405: DLM=double-precision multiply
|
|
this.adjustABFull(); // FOR NOW, just do SP multiply
|
|
this.BROF = 0; // wipe out the first mantissa extension
|
|
this.adjustBFull(); // get second mantissa
|
|
this.S--; // wipe out the second mantissa extension
|
|
this.singlePrecisionMultiply();
|
|
this.A = this.B; // move high-order result to A
|
|
this.AROF = 1;
|
|
this.B = 0; // set low-order result to 0 in B
|
|
break;
|
|
|
|
case 0x08: // 1005: DLD=double-precision floating divide
|
|
this.adjustABFull(); // FOR NOW, just do SP divide
|
|
this.BROF = 0; // wipe out the first mantissa extension
|
|
this.adjustBFull(); // get second mantissa
|
|
this.S--; // wipe out the second mantissa extension
|
|
this.singlePrecisionDivide();
|
|
this.A = this.B; // move high-order result to A
|
|
this.AROF = 1;
|
|
this.B = 0; // set low-order result to 0 in B
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x09: // XX11: control state and communication ops
|
|
switch (variant) {
|
|
case 0x01: // 0111: PRL=Program Release
|
|
this.adjustAFull();
|
|
t1 = this.A;
|
|
if (t1 < 0x800000000000) { // it's an operand
|
|
this.computeRelativeAddr(t1, 0);
|
|
t2 = 1;
|
|
} else if (this.presenceTest(t1)) {
|
|
this.M = t1 % 0x8000; // present descriptor
|
|
t2 = 1;
|
|
} else { // absent descriptor
|
|
t2 = 0;
|
|
}
|
|
if (t2) {
|
|
this.loadAviaM(); // fetch IOD
|
|
if (this.NCSF) {
|
|
if (this.A % 0x10000000 < 0x8000000) { // test continuity bit, [20:1]
|
|
this.I = (this.I & 0x0F) | 0x50; // set I07/5: program release
|
|
} else {
|
|
this.I = (this.I & 0x0F) | 0x60; // set I07/6: continuity bit
|
|
}
|
|
this.cc.signalInterrupt();
|
|
this.A = this.M;
|
|
this.M = this.R*64 + 9; // store IOD address in PRT[9]
|
|
this.storeAviaM();
|
|
} else {
|
|
this.A = this.cc.bitReset(this.A, 2);
|
|
this.storeAviaM();
|
|
}
|
|
this.AROF = 0;
|
|
}
|
|
break;
|
|
|
|
case 0x02: // 0211: ITI=Interrogate Interrupt
|
|
if (this.cc.IAR && !this.NCSF) { // control-state only
|
|
this.C = this.cc.IAR;
|
|
this.L = 0;
|
|
this.S = 0x40; // stack address @100
|
|
this.cc.clearInterrupt();
|
|
this.PROF = 0; // require fetch at SECL
|
|
}
|
|
break;
|
|
|
|
case 0x04: // 0411: RTR=Read Timer
|
|
if (!this.NCSF) { // control-state only
|
|
this.adjustAEmpty();
|
|
this.A = this.cc.readTimer();
|
|
this.AROF = 1;
|
|
}
|
|
break;
|
|
|
|
case 0x08: // 1011: COM=Communicate
|
|
if (this.NCSF) { // no-op in control state
|
|
this.M = this.R*64 + 9; // address = R+@11
|
|
if (this.AROF) {
|
|
this.storeAviaM(); // [M] = A
|
|
this.AROF = 0;
|
|
} else {
|
|
this.adjustBFull();
|
|
this.storeBviaM(); // [M] = B
|
|
this.BROF = 0;
|
|
}
|
|
this.I = (this.I & 0x0F) | 0x40; // set I07: communicate
|
|
this.cc.signalInterrupt();
|
|
}
|
|
break;
|
|
|
|
case 0x11: // 2111: IOR=I/O Release
|
|
if (!this.NCSF) { // no-op in normal state
|
|
this.adjustAFull();
|
|
t1 = this.A;
|
|
if (t1 < 0x800000000000) { // it's an operand
|
|
this.computeRelativeAddr(t1, 0);
|
|
t2 = 1;
|
|
} else if (t1 % 0x400000000000 >= 0x200000000000) {
|
|
this.M = t1 % 0x8000; // present descriptor
|
|
t2 = 1;
|
|
} else {
|
|
// for an absent descriptor, just leave it on the stack
|
|
t2 = 0;
|
|
}
|
|
if (t2) {
|
|
this.loadAviaM();
|
|
this.A = this.cc.bitSet(this.A, 2);
|
|
this.storeAviaM();
|
|
this.AROF = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x12: // 2211: HP2=Halt Processor 2
|
|
if (!this.NCSF) { // 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);
|
|
break;
|
|
|
|
case 0x1C: // 3411: SFT=Store for Test
|
|
this.storeForInterrupt(1);
|
|
break;
|
|
|
|
case 0x21: // 4111: IP1=Initiate Processor 1
|
|
if (!this.NCSF) { // control-state only
|
|
this.initiate(0);
|
|
}
|
|
break;
|
|
|
|
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) {
|
|
this.storeBviaM(); // [M] = B
|
|
this.BROF = 0;
|
|
} else {
|
|
this.adjustAFull();
|
|
this.storeAviaM(); // [M] = A
|
|
this.AROF = 0;
|
|
}
|
|
this.cc.initiateP2();
|
|
this.cycleLimit = 0; // give P2 a chance to run
|
|
}
|
|
break;
|
|
|
|
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) {
|
|
this.storeBviaM(); // [M] = B
|
|
this.BROF = 0;
|
|
} else {
|
|
this.adjustAFull();
|
|
this.storeAviaM(); // [M] = A
|
|
this.AROF = 0;
|
|
}
|
|
this.cc.initiateIO(); // let CentralControl choose the I/O Unit
|
|
this.cycleLimit = 0; // give the I/O a chance to start
|
|
}
|
|
break;
|
|
|
|
case 0x29: // 5111: IFT=Initiate For Test
|
|
if (!this.NCSF) { // control-state only
|
|
this.initiate(1);
|
|
}
|
|
break;
|
|
} // end switch for XX11 ops
|
|
break;
|
|
|
|
case 0x0D: // XX15: logical (bitmask) ops
|
|
switch (variant) {
|
|
case 0x01: // 0115: LNG=logical negate
|
|
this.adjustAFull();
|
|
t1 = this.A % 0x1000000;
|
|
t2 = (this.A - t1) / 0x1000000;
|
|
this.A = (t2 ^ 0x7FFFFF)*0x1000000 + (t1 ^ 0xFFFFFF);
|
|
break;
|
|
|
|
case 0x02: // 0215: LOR=logical OR
|
|
this.adjustABFull();
|
|
t1 = this.A % 0x1000000;
|
|
t2 = (this.A - t1) / 0x1000000;
|
|
t3 = this.B % 0x1000000;
|
|
t4 = (this.B - t3) / 0x1000000;
|
|
this.A = (t4 | (t2 & 0x7FFFFF))*0x1000000 + (t1 | t3);
|
|
this.BROF = 0;
|
|
break;
|
|
|
|
case 0x04: // 0415: LND=logical AND
|
|
this.adjustABFull();
|
|
t1 = this.A % 0x1000000;
|
|
t2 = (this.A - t1) / 0x1000000;
|
|
t3 = this.B % 0x1000000;
|
|
t4 = (this.B - t3) / 0x1000000;
|
|
this.A = ((t4 & 0x800000) | (t2 & t4 & 0x7FFFFF))*0x1000000 + (t1 & t3);
|
|
this.BROF = 0;
|
|
break;
|
|
|
|
case 0x08: // 1015: LQV=logical EQV
|
|
this.cycleCount += 16;
|
|
this.adjustABFull();
|
|
t1 = this.A % 0x1000000;
|
|
t2 = (this.A - t1) / 0x1000000;
|
|
t3 = this.B % 0x1000000;
|
|
t4 = (this.B - t3) / 0x1000000;
|
|
this.B = ((t4 & 0x800000) | ((~(t2 ^ t4)) & 0x7FFFFF))*0x1000000 + ((~(t1 ^ t3)) & 0xFFFFFF);
|
|
this.AROF = 0;
|
|
break;
|
|
|
|
case 0x10: // 2015: MOP=reset flag bit (make operand)
|
|
this.adjustAFull();
|
|
this.A %= 0x800000000000;
|
|
break;
|
|
|
|
case 0x20: // 4015: MDS=set flag bit (make descriptor)
|
|
this.adjustAFull();
|
|
this.A = this.A % 0x800000000000 + 0x800000000000; // set [0:1]
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x11: // XX21: load & store ops
|
|
switch (variant) {
|
|
case 0x01: // 0121: CID=Conditional integer store destructive
|
|
this.integerStore(1, 1);
|
|
break;
|
|
|
|
case 0x02: // 0221: CIN=Conditional integer store nondestructive
|
|
this.integerStore(1, 0);
|
|
break;
|
|
|
|
case 0x04: // 0421: STD=Store destructive
|
|
this.adjustABFull();
|
|
if (this.A < 0x800000000000) { // it's an operand
|
|
this.computeRelativeAddr(this.A, 0);
|
|
this.storeBviaM();
|
|
this.AROF = this.BROF = 0;
|
|
} else { // it's a descriptor
|
|
if (this.presenceTest(this.A)) {
|
|
this.M = this.A % 0x8000;
|
|
this.storeBviaM();
|
|
this.AROF = this.BROF = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x08: // 1021: SND=Store nondestructive
|
|
this.adjustABFull();
|
|
if (this.A < 0x800000000000) { // it's an operand
|
|
this.computeRelativeAddr(this.A, 0);
|
|
this.storeBviaM();
|
|
this.AROF = 0;
|
|
} else { // it's a descriptor
|
|
if (this.presenceTest(this.A)) {
|
|
this.M = this.A % 0x8000;
|
|
this.storeBviaM();
|
|
this.AROF = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x10: // 2021: LOD=Load operand
|
|
this.adjustAFull();
|
|
if (this.A < 0x800000000000) { // simple operand
|
|
this.computeRelativeAddr(this.A, 1);
|
|
this.loadAviaM();
|
|
} else if (this.presenceTest(this.A)) {
|
|
this.M = this.A % 0x8000; // present descriptor
|
|
this.loadAviaM();
|
|
}
|
|
break;
|
|
|
|
case 0x21: // 4121: ISD=Integer store destructive
|
|
this.integerStore(0, 1);
|
|
break;
|
|
|
|
case 0x22: // 4221: ISN=Integer store nondestructive
|
|
this.integerStore(0, 0);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x15: // XX25: comparison & misc. stack ops
|
|
switch (variant) {
|
|
case 0x01: // 0125: GEQ=compare B greater or equal to A
|
|
this.B = (this.singlePrecisionCompare() >= 0 ? 1 : 0);
|
|
break;
|
|
|
|
case 0x02: // 0225: GTR=compare B greater to A
|
|
this.B = (this.singlePrecisionCompare() > 0 ? 1 : 0);
|
|
break;
|
|
|
|
case 0x04: // 0425: NEQ=compare B not equal to A
|
|
this.B = (this.singlePrecisionCompare() != 0 ? 1 : 0);
|
|
break;
|
|
|
|
case 0x08: // 1025: XCH=exchange TOS words
|
|
this.exchangeTOS();
|
|
break;
|
|
|
|
case 0x0C: // 1425: FTC=F field to core field
|
|
this.adjustABFull();
|
|
t1 = (this.A % 0x40000000) >>> 15;
|
|
this.B -= this.B % 0x8000 - t1;
|
|
this.AROF = 0;
|
|
break;
|
|
|
|
case 0x10: // 2025: DUP=Duplicate TOS
|
|
if (this.AROF) {
|
|
this.adjustBEmpty();
|
|
this.B = this.A;
|
|
this.BROF = 1;
|
|
} else {
|
|
this.adjustBFull();
|
|
this.A = this.B;
|
|
this.AROF = 1;
|
|
}
|
|
break;
|
|
|
|
case 0x1C: // 3425: FTF=F field to F field
|
|
this.adjustABFull();
|
|
t1 = (this.A % 0x40000000 - this.A % 0x8000);
|
|
t2 = (this.B % 0x40000000 - this.B % 0x8000);
|
|
this.B -= t2 - t1;
|
|
this.AROF = 0;
|
|
break;
|
|
|
|
case 0x21: // 4125: LEQ=compare B less or equal to A
|
|
this.B = (this.singlePrecisionCompare() <= 0 ? 1 : 0);
|
|
break;
|
|
|
|
case 0x22: // 4225: LSS=compare B less to A
|
|
this.B = (this.singlePrecisionCompare() < 0 ? 1 : 0);
|
|
break;
|
|
|
|
case 0x24: // 4425: EQL=compare B equal to A
|
|
this.B = (this.singlePrecisionCompare() == 0 ? 1 : 0);
|
|
break;
|
|
|
|
case 0x2C: // 5425: CTC=core field to C field
|
|
this.adjustABFull();
|
|
this.B -= this.B % 0x8000 - this.A % 0x8000;
|
|
this.AROF = 0;
|
|
break;
|
|
|
|
case 0x3C: // 7425: CTF=core field to F field
|
|
this.adjustABFull();
|
|
t2 = (this.B % 0x40000000 - this.B % 0x8000);
|
|
this.B -= t2 - (this.A % 0x8000)*0x8000;
|
|
this.AROF = 0;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x19: // XX31: branch, sign-bit, interrogate ops
|
|
switch (variant) {
|
|
case 0x01: // 0131: BBC=branch backward conditional
|
|
this.adjustABFull();
|
|
if (this.B % 0x02) {
|
|
this.AROF = this.BROF = 0; // true => no branch
|
|
} else {
|
|
this.BROF = 0;
|
|
if (this.A < 0x800000000000) { // simple operand
|
|
this.jumpSyllables(-(this.A % 0x1000));
|
|
this.AROF = 0;
|
|
} else { // descriptor
|
|
if (this.L == 0) {
|
|
this.C--; // adjust for Inhibit Fetch
|
|
}
|
|
if (this.presenceTest(this.A)) {
|
|
this.C = this.A % 0x8000;
|
|
this.L = 0;
|
|
this.PROF = 0; // require fetch at SECL
|
|
this.AROF = 0;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x02: // 0231: BFC=branch forward conditional
|
|
this.adjustABFull();
|
|
if (this.B % 0x02) {
|
|
this.AROF = this.BROF = 0; // true => no branch
|
|
} else {
|
|
this.BROF = 0;
|
|
if (this.A < 0x800000000000) { // simple operand
|
|
this.jumpSyllables(this.A % 0x1000);
|
|
this.AROF = 0;
|
|
} else { // descriptor
|
|
if (this.L == 0) {
|
|
this.C--; // adjust for Inhibit Fetch
|
|
}
|
|
if (this.presenceTest(this.A)) {
|
|
this.C = this.A % 0x8000;
|
|
this.L = 0;
|
|
this.PROF = 0; // require fetch at SECL
|
|
this.AROF = 0;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x04: // 0431: SSN=set sign bit (set negative)
|
|
this.adjustAFull();
|
|
t1 = this.A % 0x400000000000;
|
|
t2 = (this.A - t1)/0x400000000000;
|
|
this.A = ((t2 & 0x03) | 0x01)*0x400000000000 + t1;
|
|
break;
|
|
|
|
case 0x08: // 1031: CHS=change sign bit
|
|
this.adjustAFull();
|
|
t1 = this.A % 0x400000000000;
|
|
t2 = (this.A - t1)/0x400000000000;
|
|
this.A = ((t2 & 0x03) ^ 0x01)*0x400000000000 + t1;
|
|
break;
|
|
|
|
case 0x10: // 2031: TOP=test flag bit (test for operand)
|
|
this.adjustAEmpty();
|
|
this.adjustBFull();
|
|
this.A = (this.B - this.B % 0x800000000000 ? 0 : 1);
|
|
this.AROF = 1;
|
|
break;
|
|
|
|
case 0x11: // 2131: LBC=branch backward word conditional
|
|
this.adjustABFull();
|
|
if (this.B % 0x02) {
|
|
this.AROF = this.BROF = 0; // true => no branch
|
|
} else {
|
|
this.BROF = 0;
|
|
if (this.L == 0) {
|
|
this.C--; // adjust for Inhibit Fetch
|
|
}
|
|
if (this.A < 0x800000000000) { // simple operand
|
|
this.jumpWords(-(this.A % 0x0400));
|
|
this.AROF = 0;
|
|
} else { // descriptor
|
|
if (this.presenceTest(this.A)) {
|
|
this.C = this.A % 0x8000;
|
|
this.L = 0;
|
|
this.PROF = 0; // require fetch at SECL
|
|
this.AROF = 0;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x12: // 2231: LFC=branch forward word conditional
|
|
this.adjustABFull();
|
|
if (this.B % 0x02) {
|
|
this.AROF = this.BROF = 0; // true => no branch
|
|
} else {
|
|
this.BROF = 0;
|
|
if (this.L == 0) {
|
|
this.C--; // adjust for Inhibit Fetch
|
|
}
|
|
if (this.A < 0x800000000000) { // simple operand
|
|
this.jumpWords(this.A % 0x0400);
|
|
this.AROF = 0;
|
|
} else { // descriptor
|
|
if (this.presenceTest(this.A)) {
|
|
this.C = this.A % 0x8000;
|
|
this.L = 0;
|
|
this.PROF = 0; // require fetch at SECL
|
|
this.AROF = 0;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x14: // 2431: TUS=interrogate peripheral status
|
|
this.adjustAEmpty();
|
|
this.A = this.cc.interrogateUnitStatus();
|
|
this.AROF = 1;
|
|
break;
|
|
|
|
case 0x21: // 4131: BBW=branch backward unconditional
|
|
this.adjustAFull();
|
|
if (this.A < 0x800000000000) { // simple operand
|
|
this.jumpSyllables(-(this.A % 0x1000));
|
|
this.AROF = 0;
|
|
} else { // descriptor
|
|
if (this.L == 0) {
|
|
this.C--; // adjust for Inhibit Fetch
|
|
}
|
|
if (this.presenceTest(this.A)) {
|
|
this.C = this.A % 0x8000;
|
|
this.L = 0;
|
|
this.PROF = 0; // require fetch at SECL
|
|
this.AROF = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x22: // 4231: BFW=branch forward unconditional
|
|
this.adjustAFull();
|
|
if (this.A < 0x800000000000) { // simple operand
|
|
this.jumpSyllables(this.A % 0x1000);
|
|
this.AROF = 0;
|
|
} else { // descriptor
|
|
if (this.L == 0) {
|
|
this.C--; // adjust for Inhibit Fetch
|
|
}
|
|
if (this.presenceTest(this.A)) {
|
|
this.C = this.A % 0x8000;
|
|
this.L = 0;
|
|
this.PROF = 0; // require fetch at SECL
|
|
this.AROF = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x24: // 4431: SSP=reset sign bit (set positive)
|
|
this.adjustAFull();
|
|
t1 = this.A % 0x400000000000;
|
|
t2 = (this.A - t1)/0x400000000000;
|
|
this.A = (t2 & 0x02)*0x400000000000 + t1;
|
|
break;
|
|
|
|
case 0x31: // 6131: LBU=branch backward word unconditional
|
|
this.adjustAFull();
|
|
if (this.L == 0) {
|
|
this.C--; // adjust for Inhibit Fetch
|
|
}
|
|
if (this.A < 0x800000000000) { // simple operand
|
|
this.jumpWords(-(this.A % 0x0400));
|
|
this.AROF = 0;
|
|
} else { // descriptor
|
|
if (this.presenceTest(this.A)) {
|
|
this.C = this.A % 0x8000;
|
|
this.L = 0;
|
|
this.PROF = 0; // require fetch at SECL
|
|
this.AROF = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x32: // 6231: LFU=branch forward word unconditional
|
|
this.adjustAFull();
|
|
if (this.L == 0) {
|
|
this.C--; // adjust for Inhibit Fetch
|
|
}
|
|
if (this.A < 0x800000000000) { // simple operand
|
|
this.jumpWords(this.A % 0x0400);
|
|
this.AROF = 0;
|
|
} else { // descriptor
|
|
if (this.presenceTest(this.A)) {
|
|
this.C = this.A % 0x8000;
|
|
this.L = 0;
|
|
this.PROF = 0; // require fetch at SECL
|
|
this.AROF = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x34: // 6431: TIO=interrogate I/O channel
|
|
this.adjustAEmpty();
|
|
this.A = this.cc.interrogateIOChannel();
|
|
this.AROF = 1;
|
|
break;
|
|
|
|
case 0x38: // 7031: FBS=stack search for flag
|
|
this.adjustAFull();
|
|
this.M = this.A % 0x8000;
|
|
do {
|
|
this.cycleCount += 2; // approximate the timing
|
|
this.loadAviaM();
|
|
if (this.A < 0x800000000000) {
|
|
this.M = (this.M+1) % 0x8000;
|
|
} else {
|
|
this.A = t1 = this.M + 0xA00000000000;
|
|
break; // flag bit found: stop the search
|
|
}
|
|
} while (true);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x1D: // XX35: exit & return ops
|
|
switch (variant) {
|
|
case 0x01: // 0135: BRT=branch return
|
|
this.adjustAEmpty();
|
|
if (!this.BROF) {
|
|
this.Q |= 0x04; // Q03F: not used, except for display purposes
|
|
this.adjustBFull();
|
|
}
|
|
if (this.presenceTest(this.B)) {
|
|
this.S = (this.B % 0x40000000) >>> 15;
|
|
this.C = this.B % 0x8000;
|
|
this.L = 0;
|
|
this.PROF = 0; // require fetch at SECL
|
|
this.loadBviaS(); // B = [S], fetch MSCW
|
|
this.S--;
|
|
this.applyMSCW(this.B);
|
|
this.BROF = 0;
|
|
}
|
|
break;
|
|
|
|
case 0x02: // 0235: RTN=return normal
|
|
this.adjustAFull();
|
|
// If A is an operand or a present descriptor, proceed with the return,
|
|
// otherwise throw a p-bit interrupt (this isn't well-documented)
|
|
if (this.A < 0x800000000000 || this.presenceTest(this.A)) {
|
|
this.S = this.F;
|
|
this.loadBviaS(); // B = [S], fetch the RCW
|
|
switch (this.exitSubroutine(0)) {
|
|
case 0:
|
|
this.X = 0;
|
|
this.operandCall();
|
|
break;
|
|
case 1:
|
|
this.Q |= 0x10; // set Q05F, for display only
|
|
this.X = 0;
|
|
this.descriptorCall();
|
|
break;
|
|
case 2: // flag-bit interrupt occurred, do nothing
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x04: // 0435: XIT=exit procedure
|
|
this.AROF = 0;
|
|
this.S = this.F;
|
|
this.loadBviaS(); // B = [S], fetch the RCW
|
|
this.exitSubroutine(0);
|
|
break;
|
|
|
|
case 0x0A: // 1235: RTS=return special
|
|
this.adjustAFull();
|
|
// If A is an operand or a present descriptor, proceed with the return,
|
|
// otherwise throw a p-bit interrupt (this isn't well-documented)
|
|
if (this.A < 0x800000000000 || this.presenceTest(this.A)) {
|
|
// Note that RTS assumes the RCW is pointed to by S, not F
|
|
this.loadBviaS(); // B = [S], fetch the RCW
|
|
switch (this.exitSubroutine(0)) {
|
|
case 0:
|
|
this.X = 0;
|
|
this.operandCall();
|
|
break;
|
|
case 1:
|
|
this.Q |= 0x10; // set Q05F, for display only
|
|
this.X = 0;
|
|
this.descriptorCall();
|
|
break;
|
|
case 2: // flag-bit interrupt occurred, do nothing
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x21: // XX41: index, mark stack, etc.
|
|
switch (variant) {
|
|
case 0x01: // 0141: INX=index
|
|
this.adjustABFull();
|
|
t1 = this.A % 0x8000;
|
|
this.M = (t1 + this.B % 0x8000) % 0x8000;
|
|
this.A += this.M - t1;
|
|
this.BROF = 0;
|
|
break;
|
|
|
|
case 0x02: // 0241: COC=construct operand call
|
|
this.exchangeTOS();
|
|
this.A = this.A % 0x800000000000 + 0x800000000000; // set [0:1]
|
|
this.operandCall();
|
|
break;
|
|
|
|
case 0x04: // 0441: MKS=mark stack
|
|
this.adjustABEmpty();
|
|
this.B = this.buildMSCW();
|
|
this.BROF = 1;
|
|
this.adjustBEmpty();
|
|
this.F = this.S;
|
|
if (!this.MSFF) {
|
|
if (this.SALF) { // store the MSCW at R+7
|
|
this.M = this.R*64 + 7;
|
|
this.storeBviaM(); // [M] = B
|
|
}
|
|
this.MSFF = 1;
|
|
}
|
|
break;
|
|
|
|
case 0x0A: // 1241: CDC=construct descriptor call
|
|
this.exchangeTOS();
|
|
this.A = this.A % 0x800000000000 + 0x800000000000; // set [0:1]
|
|
this.descriptorCall();
|
|
break;
|
|
|
|
case 0x11: // 2141: SSF=F & S register set/store
|
|
this.adjustABFull();
|
|
switch (this.A % 0x04) {
|
|
case 0: // store F into B.[18:15]
|
|
this.B -= (this.B % 0x40000000 - this.B % 0x8000) - this.F*0x8000;
|
|
break;
|
|
case 1: // store S into B.[33:15]
|
|
this.B -= this.B % 0x8000 - this.S;
|
|
break;
|
|
case 2: // set F from B.[18:15]
|
|
this.F = (this.B % 0x40000000) >>> 15;
|
|
this.SALF = 1;
|
|
this.BROF = 0;
|
|
break;
|
|
case 3: // set S from B.[33:15]
|
|
this.S = this.B % 0x8000;
|
|
this.BROF = 0;
|
|
break;
|
|
}
|
|
this.AROF = 0;
|
|
break;
|
|
|
|
case 0x15: // 2541: LLL=link list look-up
|
|
this.adjustABFull();
|
|
t1 = this.A % 0x8000000000; // test value
|
|
this.M = this.B % 0x8000; // starting link address
|
|
do {
|
|
this.cycleCount += 2; // approximate the timing
|
|
this.loadBviaM();
|
|
t2 = this.B % 0x8000000000;
|
|
if (t2 < t1) {
|
|
this.M = t2 % 0x8000;
|
|
} else {
|
|
this.A = this.M + 0xA00000000000;
|
|
break; // B >= A: stop look-up
|
|
}
|
|
} while (true);
|
|
break;
|
|
|
|
case 0x24: // 4441: CMN=enter character mode inline
|
|
this.enterCharModeInline();
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x25: // XX45: ISO=Variable Field Isolate op
|
|
this.adjustAFull();
|
|
t2 = variant >>> 3; // number of whole chars
|
|
if (t2) {
|
|
t1 = this.G*6 + this.H; // starting source bit position
|
|
t2 = t2*6 - (variant & 7) - this.H; // number of bits
|
|
if (t1+t2 <= 48) {
|
|
this.A = this.cc.fieldIsolate(this.A, t1, t2);
|
|
} else { // handle wrap-around in the source value
|
|
this.A = this.cc.fieldInsert(
|
|
this.cc.fieldIsolate(this.A, 0, t2-48+t1, t1+t2-48), 48-t2, 48-t1,
|
|
this.cc.fieldIsolate(this.A, t1, 48-t1));
|
|
}
|
|
// approximate the shift cycle counts
|
|
this.cycleCount += (variant >>> 3) + (variant & 7) + this.G + this.H;
|
|
this.G = (this.G + variant >>> 3) & 7;
|
|
this.H = 0;
|
|
}
|
|
break;
|
|
|
|
case 0x29: // XX51: delete & conditional branch ops
|
|
if (variant < 4) { // 0051=DEL: delete TOS (or field branch with zero-length field)
|
|
if (this.AROF) {
|
|
this.AROF = 0;
|
|
} else if (this.BROF) {
|
|
this.BROF = 0;
|
|
} else {
|
|
this.S--;
|
|
}
|
|
} else {
|
|
this.adjustABFull();
|
|
t2 = variant >>> 2; // field length (1-15 bits)
|
|
t1 = this.cc.fieldIsolate(this.B, this.G*6+this.H, t2);
|
|
this.cycleCount += this.G + this.H + (t2 >>> 1); // approximate the shift counts
|
|
this.AROF = 0; // A is unconditionally empty at end
|
|
|
|
switch (variant & 0x03) {
|
|
case 0x02: // X251/X651: CFD=non-zero field branch forward destructive
|
|
this.BROF = 0;
|
|
// no break: fall through
|
|
case 0x00: // X051/X451: CFN=non-zero field branch forward nondestructive
|
|
if (t1) {
|
|
if (this.A < 0x800000000000) { // simple operand
|
|
this.jumpSyllables(this.A % 0x1000);
|
|
} else { // descriptor
|
|
if (this.L == 0) {
|
|
this.C--; // adjust for Inhibit Fetch
|
|
}
|
|
if (this.presenceTest(this.A)) {
|
|
this.C = this.A % 0x8000;
|
|
this.L = 0;
|
|
this.PROF = 0; // require fetch at SEQL
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x03: // X351/X751: CBD=non-zero field branch backward destructive
|
|
this.BROF = 0;
|
|
// no break: fall through
|
|
case 0x01: // X151/X551: CBN=non-zero field branch backward nondestructive
|
|
if (t1) {
|
|
if (this.A < 0x800000000000) { // simple operand
|
|
this.jumpSyllables(-(this.A % 0x1000));
|
|
} else { // descriptor
|
|
if (this.L == 0) {
|
|
this.C--; // adjust for Inhibit Fetch
|
|
}
|
|
if (this.presenceTest(this.A)) {
|
|
this.C = this.A % 0x8000;
|
|
this.L = 0;
|
|
this.PROF = 0; // require fetch at SEQL
|
|
}
|
|
}
|
|
}
|
|
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.adjustABFull();
|
|
if (variant > 0) {
|
|
t1 = this.G*6 + this.H; // A register starting bit nr
|
|
if (t1+variant > 48) {
|
|
variant = 48-t1;
|
|
}
|
|
t2 = this.K*6 + this.V; // B register starting bit nr
|
|
if (t2+variant > 48) {
|
|
variant = 48-t2;
|
|
}
|
|
this.B = this.cc.fieldTransfer(this.B, t2, variant, this.A, t1);
|
|
}
|
|
this.AROF = 0;
|
|
this.cycleCount += variant + this.G + this.K; // approximate the shift counts
|
|
break;
|
|
|
|
case 0x39: // XX71: FCL=Compare Field Low
|
|
this.adjustABFull();
|
|
t1 = this.G*6 + this.H; // A register starting bit nr
|
|
if (t1+variant > 48) {
|
|
variant = 48-t1;
|
|
}
|
|
t2 = this.K*6 + this.V; // B register starting bit nr
|
|
if (t2+variant > 48) {
|
|
variant = 48-t2;
|
|
}
|
|
if (variant == 0) {
|
|
this.A = 1;
|
|
} else if (this.cc.fieldIsolate(this.B, t2, variant) < this.cc.fieldIsolate(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.adjustABFull();
|
|
t1 = this.G*6 + this.H; // A register starting bit nr
|
|
if (t1+variant > 48) {
|
|
variant = 48-t1;
|
|
}
|
|
t2 = this.K*6 + this.V; // B register starting bit nr
|
|
if (t2+variant > 48) {
|
|
variant = 48-t2;
|
|
}
|
|
if (variant == 0) {
|
|
this.A = 1;
|
|
} else if (this.cc.fieldIsolate(this.B, t2, variant) == this.cc.fieldIsolate(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.isP1 ? this.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 &= 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
|
|
} else {
|
|
// otherwise, fetch the next instruction
|
|
if (!this.PROF) {
|
|
this.loadPviaC();
|
|
}
|
|
switch (this.L) {
|
|
case 0:
|
|
this.T = (((t1=this.P) - t1 % 0x1000000000) / 0x1000000000) % 0x1000;
|
|
this.L = 1;
|
|
break;
|
|
case 1:
|
|
this.T = (((t1=this.P) - t1 % 0x1000000) / 0x1000000) % 0x1000;
|
|
this.L = 2;
|
|
break;
|
|
case 2:
|
|
this.T = (((t1=this.P) - t1 % 0x1000) / 0x1000) % 0x1000;
|
|
this.L = 3;
|
|
break;
|
|
case 3:
|
|
this.T = this.P % 0x1000;
|
|
this.L = 0;
|
|
this.C++; // assume no Inhibit Fetch for now and bump C
|
|
this.PROF = 0; // invalidate current program word
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (this.NCSF) {
|
|
this.normalCycles += this.cycleCount;
|
|
} else {
|
|
this.controlCycles += this.cycleCount;
|
|
}
|
|
} while ((this.runCycles += this.cycleCount) < this.cycleLimit);
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.schedule = function schedule() {
|
|
/* Schedules the processor running 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 after an appropriate delay, thereby
|
|
throttling the performance and allowing other modules a chance at the
|
|
single Javascript execution thread */
|
|
var clockOff = new Date().getTime();// ending time for the delay and the run() call, ms
|
|
var delayTime; // delay from/until next run() for this processor, ms
|
|
var runTime; // real-world processor running time, ms
|
|
|
|
this.scheduler = null;
|
|
delayTime = clockOff - this.delayLastStamp;
|
|
this.procSlack += delayTime;
|
|
this.delayDeltaAvg = (this.delayDeltaAvg*(B5500Processor.delaySamples-1) +
|
|
delayTime - this.delayRequested)/B5500Processor.delaySamples;
|
|
|
|
if (this.busy) {
|
|
this.cycleLimit = B5500Processor.timeSlice;
|
|
this.runCycles = 0;
|
|
|
|
this.run(); // execute syllables for the timeslice
|
|
|
|
this.delayLastStamp = clockOff = new Date().getTime();
|
|
this.totalCycles += this.runCycles;
|
|
if (!this.busy) {
|
|
this.delayRequested = 0;
|
|
} else {
|
|
runTime = this.procTime;
|
|
while (runTime < 0) {
|
|
runTime += clockOff;
|
|
}
|
|
|
|
delayTime = this.totalCycles/B5500Processor.cyclesPerMilli - runTime;
|
|
// delayTime is the number of milliseconds the processor is running ahead of
|
|
// real-world time. Web browsers have a certain minimum setTimeout() delay. If the
|
|
// 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.
|
|
|
|
this.delayRequested = delayTime;
|
|
this.scheduler = setCallback(this.schedule, this, delayTime);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500Processor.prototype.step = function step() {
|
|
/* Single-steps the processor. Normally this will cause one instruction to
|
|
be executed, but note that in the case of an interrupt or char-mode CRF, one
|
|
or two injected instructions (e.g., SFI followed by ITI) could also be executed */
|
|
|
|
this.cycleLimit = 1;
|
|
this.runCycles = 0;
|
|
|
|
this.run();
|
|
|
|
this.totalCycles += this.runCycles;
|
|
};
|