mirror of
https://github.com/pkimpel/retro-b5500.git
synced 2026-05-02 14:31:08 +00:00
781 lines
37 KiB
JavaScript
781 lines
37 KiB
JavaScript
/***********************************************************************
|
|
* retro-b5500/emulator B5500IOUnit.js
|
|
************************************************************************
|
|
* Copyright (c) 2012, Nigel Williams and Paul Kimpel.
|
|
* Licensed under the MIT License, see
|
|
* http://www.opensource.org/licenses/mit-license.php
|
|
************************************************************************
|
|
* B5500 Input/Output Unit 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 B5283 Input/Output Training Manual (1036993, January 1969)
|
|
* for details:
|
|
* http://bitsavers.org/pdf/burroughs/B5000_5500_5700/1021326_B5500_RefMan_May67.pdf
|
|
* http://bitsavers.org/pdf/burroughs/B5000_5500_5700/1036993_B5283_TrainingMan_Jan69.pdf
|
|
*
|
|
* B5500 word format: 48 bits plus (hidden) parity.
|
|
* Bit 0 is high-order, bit 47 is low-order, big-endian character ordering.
|
|
* I/O and Result Descriptors have the following general format:
|
|
* [0:1] Flag bit (1=descriptor)
|
|
* [1:1] (0=descriptor)
|
|
* [2:1] Presence bit (1=present and available in memory, 0=absent or unavailable)
|
|
* [3:5] Unit designate
|
|
* [8:10] Word count (optional, see [23:1])
|
|
* [18:1] Memory inhibit bit (1=no data transfer)
|
|
* [19:2] (not used by I/O Unit)
|
|
* [21:1] Mode bit (0=alpha, 1=binary)
|
|
* [22:1] Direction bit (0=forward, 1=reverse for mag tape, 120/132 col for printers)
|
|
* [23:1] Word count bit (0=ignore, 1=use word count in [8:10])
|
|
* [24:1] I/O bit (0=write, 1=read)
|
|
* [25:1] (not used by I/O Unit)
|
|
* [26:7] Control and error-reporting bits (depend on unit)
|
|
* [33:15] Memory address
|
|
*
|
|
************************************************************************
|
|
* 2012-12-08 P.Kimpel
|
|
* Original version, from thin air.
|
|
***********************************************************************/
|
|
"use strict";
|
|
|
|
/**************************************/
|
|
function B5500IOUnit(ioUnitID, cc) {
|
|
/* Constructor for the I/O Unit object */
|
|
|
|
this.ioUnitID = ioUnitID; // I/O Unit ID ("1", "2", "3", or "4")
|
|
this.cc = cc; // Reference back to Central Control module
|
|
|
|
this.forkHandle = null; // Reference to current setTimeout id
|
|
this.accessor = { // Memory access control block
|
|
requestorID: ioUnitID, // 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
|
|
};
|
|
|
|
// Establish a buffer for the peripheral modules to use during their I/O.
|
|
// The size is sufficient for 63 disk sectors, rounded up to the next 8KB.
|
|
this.bufferArea = new ArrayBuffer(16384);
|
|
this.buffer = new Uint8Array(this.bufferArea);
|
|
|
|
this.clear(); // Create and initialize the processor state
|
|
}
|
|
|
|
/**************************************/
|
|
|
|
B5500IOUnit.timeSlice = 5000; // Standard run() timeslice, about 5ms (we hope)
|
|
B5500IOUnit.memCycles = 6; // assume 6 us memory cycle time (the other option was 4 usec)
|
|
|
|
B5500IOUnit.BICtoANSI = [ // Index by 6-bit BIC to get 8-bit ANSI code
|
|
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37, // 00-07, @00-07
|
|
0x38,0x39,0x23,0x40,0x3F,0x3A,0x3E,0x7D, // 08-1F, @10-17
|
|
0x2B,0x41,0x42,0x43,0x44,0x45,0x46,0x47, // 10-17, @20-27
|
|
0x48,0x49,0x2E,0x5B,0x26,0x28,0x3C,0x7E, // 18-1F, @30-37
|
|
0x7C,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50, // 20-27, @40-47
|
|
0x51,0x52,0x24,0x2A,0x2D,0x29,0x3B,0x7B, // 28-2F, @50-57
|
|
0x20,0x2F,0x53,0x54,0x55,0x56,0x57,0x58, // 30-37, @60-67
|
|
0x59,0x5A,0x2C,0x25,0x21,0x3D,0x5D,0x22]; // 38-3F, @70-77
|
|
|
|
B5500IOUnit.BICtoBCLANSI = [ // Index by 6-bit BIC to get 8-bit BCL-as-ANSI code
|
|
0x23,0x31,0x32,0x33,0x34,0x35,0x36,0x37, // 00-07, @00-07
|
|
0x38,0x39,0x40,0x3F,0x30,0x3A,0x3E,0x7D, // 08-1F, @10-17
|
|
0x2C,0x2F,0x53,0x54,0x55,0x56,0x57,0x58, // 10-17, @20-27
|
|
0x59,0x5A,0x25,0x21,0x20,0x3D,0x5D,0x22, // 18-1F, @30-37
|
|
0x24,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50, // 20-27, @40-47
|
|
0x51,0x52,0x2A,0x2D,0x7C,0x29,0x3B,0x7B, // 28-2F, @50-57
|
|
0x2B,0x41,0x42,0x43,0x44,0x45,0x46,0x47, // 30-37, @60-67
|
|
0x48,0x49,0x5B,0x26,0x2E,0x28,0x3C,0x7E]; // 38-3F, @70-77
|
|
|
|
B5500IOUnit.ANSItoBIC = [ // Index by 8-bit ANSI to get 6-bit BIC (upcased, invalid=>"?")
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 00-0F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 10-1F
|
|
0x30,0x3C,0x3F,0x0A,0x2A,0x3B,0x1C,0x0C,0x1D,0x2D,0x2B,0x10,0x3A,0x2C,0x1A,0x31, // 20-2F
|
|
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0D,0x2E,0x1E,0x3D,0x0E,0x0C, // 30-3F
|
|
0x0B,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x21,0x22,0x23,0x24,0x25,0x26, // 40-4F
|
|
0x27,0x28,0x29,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x1B,0x0C,0x3E,0x0C,0x0C, // 50-5F
|
|
0x0C,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x21,0x22,0x23,0x24,0x25,0x26, // 60-6F
|
|
0x27,0x28,0x29,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x2F,0x20,0x0F,0x1F,0x0C, // 70-7F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 80-8F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 90-9F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // A0-AF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // B0-BF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // C0-CF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // D0-DF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // E0-EF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C]; // F0-FF
|
|
|
|
B5500IOUnit.BCLANSItoBIC = [ // Index by 8-bit BCL-as-ANSI to get 6-bit BIC (upcased, invalid=>"?")
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 00-0F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 10-1F
|
|
0x1C,0x1B,0x1F,0x00,0x20,0x1A,0x3B,0x0C,0x3D,0x2D,0x2A,0x30,0x10,0x2B,0x3C,0x11, // 20-2F
|
|
0x0C,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0D,0x2E,0x3E,0x1D,0x0E,0x0B, // 30-3F
|
|
0x0A,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x21,0x22,0x23,0x24,0x25,0x26, // 40-4F
|
|
0x27,0x28,0x29,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x3A,0x0C,0x1E,0x0C,0x0C, // 50-5F
|
|
0x0C,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x21,0x22,0x23,0x24,0x25,0x26, // 60-6F
|
|
0x27,0x28,0x29,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x2F,0x2C,0x0F,0x3F,0x0C, // 70-7F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 80-8F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // 90-9F
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // A0-AF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // B0-BF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // C0-CF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // D0-DF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // E0-EF
|
|
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C]; // F0-FF
|
|
|
|
/**************************************/
|
|
B5500IOUnit.prototype.clear = function() {
|
|
/* Initializes (and if necessary, creates) the I/O Unit state */
|
|
|
|
this.W = 0; // Memory buffer register
|
|
this.D = 0; // I/O descriptor (control) register
|
|
this.clearD(); // clear the D-register exploded fields
|
|
|
|
this.CC = 0; // Character counter (3 bits)
|
|
this.IB = 0; // Input buffer register (6 bits+parity)
|
|
this.IR = 0; // Tape read register (6 bits+parity)
|
|
this.OB = 0; // Output buffer register (6 bits+parity)
|
|
this.WB = 0; // Tape write register (6 bits+parity)
|
|
this.LP = 0; // Longitudinal parity register (6 bits+parity)
|
|
this.SC = 0; // Sequence counter (4 bits)
|
|
this.PC = 0; // Pulse counter register (6 bits, 1MHz rate)
|
|
|
|
this.AOFF = 0; // Address overflow FF
|
|
this.EXNF = 0; // W-contents FF (0=has address, 1=has descriptor)
|
|
this.HOLF = 0; // Hold-over FF (for alpha vs. binary card read)
|
|
this.LCHF = 0; // Last-character FF (triggers RD construction)
|
|
this.LPWF = 0; // (unknown)
|
|
this.MANF = 0; // Memory access needed FF
|
|
this.MAOF = 0; // Memory access obtained FF
|
|
this.OBCF = 0; // (unknown)
|
|
this.STRF = 0; // Strobe FF
|
|
|
|
this.BKWF = 0; // Tape control FF
|
|
this.FWDF = 0; // Tape control FF
|
|
this.IM1F = 0; // Tape control FF
|
|
this.IMFF = 0; // Tape control FF
|
|
this.PUCF = 0; // Tape control FF
|
|
this.RCNF = 0; // Tape control FF
|
|
this.SHOF = 0; // Tape control FF
|
|
this.SKFF = 0; // Tape control FF
|
|
this.VRCF = 0; // Tape control FF
|
|
|
|
this.IMCF = 0; // (unknown)
|
|
this.RECF = 0; // (unknown)
|
|
this.REMF = 1; // Remote FF (0=local, 1=remote and available)
|
|
|
|
this.busy = 0; // I/O Unit is busy
|
|
this.busyUnit = 0; // Peripheral unit index currently assigned to the I/O Unit
|
|
|
|
this.cycleCount = 0; // Current cycle count for this.run()
|
|
this.cycleLimit = 0; // Cycle limit for this.run()
|
|
this.totalCycles = 0; // Total cycles executed on this I/O Unit
|
|
this.ioUnitTime = 0; // Total I/O Unit running time, based on cycles executed
|
|
this.ioUnitSlack = 0; // Total I/O Unit throttling delay, milliseconds
|
|
|
|
if (this.forkHandle) {
|
|
clearTimeout(this.forkHandle);
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500IOUnit.prototype.clearD = function() {
|
|
/* Clears the D-register and the exploded field variables used internally */
|
|
|
|
this.D = 0;
|
|
this.Dunit = 0; // Unit designate field (5 bits)
|
|
this.DwordCount = 0; // Word count field (10 bits)
|
|
this.D18F = 0; // Memory inhibit bit (0=transfer, 1=no transfer)
|
|
this.D21F = 0; // Mode bit (0=alpha, 1=binary)
|
|
this.D22F = 0; // Direction bit (0=forward), etc.
|
|
this.D23F = 0; // Word counter bit (0=ignore, 1=use)
|
|
this.D24F = 0; // I/O bit (0=write, 1=read)
|
|
this.D26F = 0; // Memory address error bit
|
|
this.D27F = 0; // Device error bit 1
|
|
this.D28F = 0; // Device error bit 2
|
|
this.D29F = 0; // Device error bit 3
|
|
this.D30F = 0; // Unit not-ready bit
|
|
this.D31F = 0; // Memory parity error on descriptor fetch bit
|
|
this.D32F = 0; // Unit busy bit
|
|
this.Daddress = 0; // Memory transfer address (15 bits)
|
|
};
|
|
|
|
/**************************************/
|
|
B5500IOUnit.prototype.fetch = function(addr) {
|
|
/* Fetch a word from memory at address "addr" and leave it in the W register.
|
|
Returns 1 if a memory access error occurred, 0 if no error */
|
|
var acc = this.accessor; // get a local reference to the accessor object
|
|
|
|
acc.addr = addr;
|
|
this.cc.fetch(acc);
|
|
this.W = acc.word;
|
|
|
|
this.cycleCount += B5500IOUnit.memCycles;
|
|
if (acc.MAED) {
|
|
this.D26F = 1; // set memory address error
|
|
return 1;
|
|
} else if (acc.MPED) {
|
|
this.D29F = 1; // set memory parity error on data transfer
|
|
return 1;
|
|
} else {
|
|
return 0; // no error
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500IOUnit.prototype.store = function(addr) {
|
|
/* Store a word in memory at address "addr" from the W register.
|
|
Returns 1 if a memory access error occurred, 0 if no error */
|
|
var acc = this.accessor; // get a local reference to the accessor object
|
|
|
|
acc.addr = addr;
|
|
acc.word = this.W;
|
|
this.cc.store(acc);
|
|
|
|
this.cycleCount += B5500IOUnit.memCycles;
|
|
if (acc.MAED) {
|
|
this.D26F = 1; // set memory address error
|
|
return 1;
|
|
} else {
|
|
return 0; // no error
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500IOUnit.prototype.fetchBuffer = function(mode, words) {
|
|
/* Fetches words from memory starting at this.Daddress and coverts the
|
|
BIC characters to ANSI or BCLANSI in this.buffer. "mode": 0=BCLANSI, 1=ANSI;
|
|
"words": maximum number of words to transfer. At exit, updates this.Daddress
|
|
with the final transfer address+1. If this.D23F, updates this.wordCount
|
|
with any remaining count.
|
|
Returns the number of characters fetched into the buffer */
|
|
var addr = this.Daddress; // local copy of memory address
|
|
var buf = this.buffer; // local pointer to buffer
|
|
var c; // current character code
|
|
var count = 0; // number of characters fetched
|
|
var done = false; // loop control
|
|
var overflow = false; // memory address overflowed max
|
|
var s; // character shift counter
|
|
var table = (mode ? B5500IOUnit.BICtoANSI : B5500IOUnit.BICtoBCLANSI);
|
|
var w; // local copy of this.W
|
|
|
|
do { // loop through the words
|
|
if (words <= 0) {
|
|
done = true;
|
|
} else {
|
|
words--;
|
|
if (overflow) {
|
|
this.AOFF = 1; // for display only
|
|
this.D26F = 1; // address overflow: set invalid address error
|
|
done = true;
|
|
} else if (!this.fetch(addr)) { // fetch the next word from memory
|
|
w = this.W; // fill the buffer with this word's characters
|
|
for (s=0; s<8; s++) {
|
|
c = (w - (w %= 0x40000000000))/0x40000000000;
|
|
buf[count++] = table[c];
|
|
w *= 64; // shift word left 6 bits
|
|
} // for s
|
|
}
|
|
if (addr < 0x7FFF) {
|
|
addr++;
|
|
} else {
|
|
overflow = true;
|
|
}
|
|
}
|
|
} while (!done);
|
|
|
|
this.Daddress = addr;
|
|
if (this.D23F) {
|
|
this.DwordCount = words & 0x1FF;
|
|
}
|
|
return count;
|
|
};
|
|
|
|
/**************************************/
|
|
B5500IOUnit.prototype.fetchBufferWithGM = function(mode, words) {
|
|
/* Fetches words from memory starting at this.Daddress and coverts the
|
|
BIC characters to ANSI or BCLANSI in this.buffer. "mode": 0=BCLANSI, 1=ANSI;
|
|
"words": maximum number of words to transfer. The transfer can be terminated
|
|
by a group-mark code in memory. At exit, updates this.Daddress with the
|
|
final transfer address+1. If this.D23F, updates this.wordCount
|
|
with any remaining count.
|
|
Returns the number of characters fetched into the buffer */
|
|
var addr = this.Daddress; // local copy of memory address
|
|
var buf = this.buffer; // local pointer to buffer
|
|
var c; // current character code
|
|
var count = 0; // number of characters fetched
|
|
var done = false; // loop control
|
|
var overflow = false; // memory address overflowed max
|
|
var s; // character shift counter
|
|
var table = (mode ? B5500IOUnit.BICtoANSI : B5500IOUnit.BICtoBCLANSI);
|
|
var w; // local copy of this.W
|
|
|
|
do { // loop through the words
|
|
if (words <= 0) {
|
|
done = true;
|
|
} else {
|
|
words--;
|
|
if (overflow) {
|
|
this.AOFF = 1; // for display only
|
|
this.D26F = 1; // address overflow: set invalid address error
|
|
done = true;
|
|
} else if (!this.fetch(addr)) { // fetch the next word from memory
|
|
w = this.W; // fill the buffer with this word's characters
|
|
for (s=0; s<8; s++) {
|
|
c = (w - (w %= 0x40000000000))/0x40000000000;
|
|
if (c == 0x1F) {
|
|
done = true; // group-mark detected
|
|
break;
|
|
} else {
|
|
buf[count++] = table[c];
|
|
w *= 64; // shift word left 6 bits
|
|
}
|
|
} // for s
|
|
}
|
|
if (addr < 0x7FFF) {
|
|
addr++;
|
|
} else {
|
|
overflow = true;
|
|
}
|
|
}
|
|
} while (!done);
|
|
|
|
this.Daddress = addr;
|
|
if (this.D23F) {
|
|
this.DwordCount = words & 0x1FF;
|
|
}
|
|
return count;
|
|
};
|
|
|
|
/**************************************/
|
|
B5500IOUnit.prototype.storeBuffer = function(chars, offset, mode, words) {
|
|
/* Converts characters in this.buffer from ANSI or BCLANSI to BIC, assembles
|
|
them into words, and stores the words into memory starting at this.Daddress.
|
|
"chars": the number of characters to store, starting at "offset" in the buffer;
|
|
"mode": 0=BCLANSI, 1=ANSI; "words": maximum number of words to transfer.
|
|
At exit, updates this.Daddress with the final transfer address+1.
|
|
If this.D23F, updates this.wordCount with any remaining count.
|
|
Returns the number of characters stored into memory from the buffer */
|
|
var addr = this.Daddress; // local copy of memory address
|
|
var buf = this.buffer; // local pointer to buffer
|
|
var c; // current character code
|
|
var count = 0; // number of characters fetched
|
|
var done = (words == 0); // loop control
|
|
var overflow = false; // memory address overflowed max
|
|
var power = 0x40000000000; // factor for character shifting into a word
|
|
var s = 0; // character shift counter
|
|
var table = (mode ? B5500IOUnit.ANSItoBIC : B5500IOUnit.BCLANSItoBIC);
|
|
var w = 0; // local copy of this.W
|
|
|
|
while (!done) { // loop through the words
|
|
if (count >= chars) {
|
|
done = true;
|
|
} else {
|
|
c = table[buf[offset+(count++)]];
|
|
w += c*power;
|
|
power /= 64;
|
|
if (++s > 7) {
|
|
this.W = w;
|
|
if (overflow) {
|
|
this.AOFF = 1; // for display only
|
|
this.D26F = 1; // address overflow: set invalid address error
|
|
done = true;
|
|
} else {
|
|
this.store(addr); // store the word in memory
|
|
}
|
|
if (addr < 0x7FFF) {
|
|
addr++;
|
|
} else {
|
|
overflow = true;
|
|
}
|
|
w = s = 0;
|
|
power = 0x40000000000;
|
|
if (--words <= 0) {
|
|
done = true;
|
|
}
|
|
}
|
|
}
|
|
} // while !done
|
|
|
|
if (s > 0 && words > 0) { // partial word left to be stored
|
|
this.W = w;
|
|
if (overflow) {
|
|
this.AOFF = 1; // for display only
|
|
this.D26F = 1; // address overflow: set invalid address error
|
|
done = true;
|
|
} else {
|
|
this.store(addr); // store the word in memory
|
|
}
|
|
words--;
|
|
if (addr < 0x7FFF) {
|
|
addr++;
|
|
}
|
|
}
|
|
|
|
this.Daddress = addr;
|
|
if (this.D23F) {
|
|
this.DwordCount = words & 0x1FF;
|
|
}
|
|
return count;
|
|
};
|
|
|
|
/**************************************/
|
|
B5500IOUnit.prototype.storeBufferWithGM = function(chars, offset, mode, words) {
|
|
/* Converts characters in this.buffer from ANSI to BIC, assembles them into
|
|
words, and stores the words into memory starting at this.Daddress.
|
|
"chars": the number of characters to store, starting at "offset" in the buffer;
|
|
"mode": 0=BCLANSI, 1=ANSI; "words": maximum number of words to transfer.
|
|
The final character stored from the buffer is followed in memory by a group-mark,
|
|
assuming the word count is not exhausted. At exit, updates this.Daddress with the
|
|
final transfer address+1.
|
|
If this.D23F, updates this.wordCount with any remaining count.
|
|
Returns the number of characters stored into memory from the buffer, plus one
|
|
for the group-mark */
|
|
var addr = this.Daddress; // local copy of memory address
|
|
var buf = this.buffer; // local pointer to buffer
|
|
var c; // current character code
|
|
var count = 0; // number of characters fetched
|
|
var done = (words == 0); // loop control
|
|
var overflow = false; // memory address overflowed max
|
|
var power = 0x40000000000; // factor for character shifting into a word
|
|
var s = 0; // character shift counter
|
|
var table = (mode ? B5500IOUnit.ANSItoBIC : B5500IOUnit.BCLANSItoBIC);
|
|
var w = 0; // local copy of this.W
|
|
|
|
while (!done) { // loop through the words
|
|
if (count >= chars) {
|
|
done = true;
|
|
} else {
|
|
c = table[buf[offset+(count++)]];
|
|
w += c*power;
|
|
power /= 64;
|
|
if (++s > 7) {
|
|
this.W = w;
|
|
if (overflow) {
|
|
this.AOFF = 1; // for display only
|
|
this.D26F = 1; // address overflow: set invalid address error
|
|
done = true;
|
|
} else {
|
|
this.store(addr); // store the word in memory
|
|
}
|
|
if (addr < 0x7FFF) {
|
|
addr++;
|
|
} else {
|
|
overflow = true;
|
|
}
|
|
w = s = 0;
|
|
power = 0x40000000000;
|
|
if (--words <= 0) {
|
|
done = true;
|
|
}
|
|
}
|
|
}
|
|
} // while !done
|
|
|
|
w += 0x1F*power; // set group mark in register
|
|
s++;
|
|
count++;
|
|
|
|
if (s > 0 && words > 0) { // partial word left to be stored
|
|
this.W = w;
|
|
if (overflow) {
|
|
this.AOFF = 1; // for display only
|
|
this.D26F = 1; // address overflow: set invalid address error
|
|
done = true;
|
|
} else {
|
|
this.store(addr); // store the word in memory
|
|
}
|
|
words--;
|
|
if (addr < 0x7FFF) {
|
|
addr++;
|
|
}
|
|
}
|
|
|
|
this.Daddress = addr;
|
|
if (this.D23F) {
|
|
this.DwordCount = words & 0x1FF;
|
|
}
|
|
return count;
|
|
};
|
|
|
|
/**************************************/
|
|
B5500IOUnit.prototype.finish = function () {
|
|
/* Called to finish an I/O operation on this I/O Unit. Constructs and stores
|
|
the result descriptor, sets the appropriate I/O Finished interrupt in CC */
|
|
|
|
this.W = this.D =
|
|
this.Dunit * 0x10000000000 +
|
|
this.DwordCount * 0x40000000 +
|
|
this.D18F * 0x20000000 +
|
|
this.D21F * 0x4000000 +
|
|
this.D22F * 0x2000000 +
|
|
this.D23F * 0x1000000 +
|
|
this.D24F * 0x800000 +
|
|
this.D26F * 0x200000 +
|
|
this.D27F * 0x100000 +
|
|
this.D28F * 0x80000 +
|
|
this.D29F * 0x40000 +
|
|
this.D30F * 0x20000 +
|
|
this.D31F * 0x10000 +
|
|
this.D32F * 0x8000 +
|
|
this.Daddress;
|
|
|
|
switch(this.ioUnitID) {
|
|
case "1":
|
|
this.store(0x0C);
|
|
this.cc.CCI08F = 1; // set I/O Finished #1
|
|
break;
|
|
case "2":
|
|
this.store(0x0D);
|
|
this.cc.CCI08F = 1; // set I/O Finished #2
|
|
break;
|
|
case "3":
|
|
this.store(0x0E);
|
|
this.cc.CCI08F = 1; // set I/O Finished #3
|
|
break;
|
|
case "4":
|
|
this.store(0x0F);
|
|
this.cc.CCI08F = 1; // set I/O Finished #4
|
|
break;
|
|
}
|
|
|
|
this.busy = 0; // zero so CC won't think I/O unit is busy
|
|
this.busyUnit = 0;
|
|
this.cc.signalInterrupt();
|
|
};
|
|
|
|
/**************************************/
|
|
B5500IOUnit.prototype.makeFinish = function(f) {
|
|
/* Utility function to create a closure for I/O finish handlers */
|
|
var that = this;
|
|
|
|
return function(mask, length) {return f.call(that, mask, length)};
|
|
};
|
|
|
|
/**************************************/
|
|
B5500IOUnit.prototype.finishGeneric = function(errorMask, length) {
|
|
/* Handles a generic I/O finish when no word-count update or input data
|
|
transfer is needed. Can also be used to apply common error mask posting
|
|
at the end of specialized finish handlers. Note that this turns off the
|
|
busyUnit mask bit in CC */
|
|
|
|
if (errorMask & 0x01) {this.D32F = 1}
|
|
if (errorMask & 0x02) {this.D31F = 1}
|
|
if (errorMask & 0x04) {this.D30F = 1}
|
|
if (errorMask & 0x08) {this.D29F = 1}
|
|
if (errorMask & 0x10) {this.D28F = 1}
|
|
if (errorMask & 0x20) {this.D27F = 1}
|
|
if (errorMask & 0x40) {this.D26F = 1}
|
|
this.cc.setUnitBusy(this.busyUnit, 0);
|
|
this.finish();
|
|
};
|
|
|
|
/**************************************/
|
|
B5500IOUnit.prototype.finishSPORead = function(errorMask, length) {
|
|
/* Handles I/O finish for a SPO keyboard input operation */
|
|
|
|
this.storeBufferWithGM(length, 0, 1, 0x7FFF);
|
|
this.finishGeneric(errorMask, length);
|
|
};
|
|
|
|
/**************************************/
|
|
B5500IOUnit.prototype.finishDiskRead = function(errorMask, length) {
|
|
/* Handles I/O finish for a DFCU data read operation */
|
|
var segWords = Math.floor((length+7)/8);
|
|
var memWords = (this.D23F ? this.DwordCount : segWords);
|
|
|
|
if (segWords < memWords) {
|
|
memWords = segWords;
|
|
}
|
|
this.storeBuffer(length, 0, this.D21F, memWords);
|
|
this.finishGeneric(errorMask, length);
|
|
};
|
|
|
|
/**************************************/
|
|
B5500IOUnit.prototype.initiateDiskIO = function(u) {
|
|
/* Initiates an I/O to the Disk File Control Unit. The disk address is fetched from
|
|
the first word of the memory area and converted to binary for the DFCU module. Read
|
|
check and interrogate operations are determined from their respective IOD bits. If
|
|
it's a read data operation, we request the specified number of segments from the disk
|
|
and will sort out word count issues in finishDiskRead(). If it's a write data operation,
|
|
we truncate or pad the data from memory as appropriate and request a write of the
|
|
specified number of segments */
|
|
var c; // address char
|
|
var memWords; // number of memory words to transfer
|
|
var p = 1; // address digit power
|
|
var w; // current memory word value
|
|
var x; // temp variable
|
|
|
|
var segAddr = 0; // disk segment address
|
|
var segs = this.LP; // I/O size in segments
|
|
var segWords = segs*30; // I/O size in words
|
|
var segChars = segWords*8; // I/O size in characters
|
|
|
|
if (this.fetch(this.Daddress)) { // fetch the segment address from first buffer word
|
|
this.finish();
|
|
} else {
|
|
this.Daddress++; // bump memory address past the seg address word
|
|
w = this.W; // convert address word to binary
|
|
for (x=0; x<7; x++) {
|
|
c = w % 0x40; // get low-order six bits of word
|
|
segAddr += (c % 0x10)*p; // isolate the numeric portion and accumulate
|
|
w = (w-c)/0x40; // shift word right six bits
|
|
p *= 10; // bump power for next digit
|
|
}
|
|
|
|
if (this.D18F) { // mem inhibit => read check operation
|
|
u.readCheck(this.makeFinish(this.finishGeneric), segChars, segAddr);
|
|
} else if (this.D23F && this.DwordCount == 0) {
|
|
if (this.D24F) { // read interrogate operation
|
|
u.readInterrogate(this.makeFinish(this.finishGeneric), segAddr);
|
|
} else { // write interrogate operation
|
|
u.writeInterrogate(this.makeFinish(this.finishGeneric), segAddr);
|
|
}
|
|
} else if (this.D24F) { // it's a read data operation
|
|
u.read(this.makeFinish(this.finishDiskRead), this.buffer, segChars, this.D21F, segAddr);
|
|
} else { // it's a write data operation
|
|
memWords = (this.D23F ? this.DwordCount : segWords);
|
|
if (segWords <= memWords) { // transfer size is limited by number of segs
|
|
this.fetchBuffer(this.D21F, segWords);
|
|
} else { // transfer size is limited by word count
|
|
x = this.fetchBuffer(this.D21F, memWords);
|
|
c = (this.D21F ? 0x00 : 0x2B); // pad "0" if binary, " " if alpha (as BCL)
|
|
while (x < segChars) { // pad remainder of buffer up to seg count
|
|
this.buffer[x++] = c;
|
|
}
|
|
}
|
|
u.write(this.makeFinish(this.finishGeneric), this.buffer, segChars, this.D21F, segAddr);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500IOUnit.prototype.forkIO = function forkIO() {
|
|
/* Asychrounously nitiates an I/O operation on this I/O Unit for a peripheral device */
|
|
var addr; // memory address
|
|
var chars; // I/O memory transfer length
|
|
var index; // unit index
|
|
var u; // peripheral unit object
|
|
var x; // temp number variable
|
|
|
|
this.forkHandle = null; // clear the setTimeout() handle
|
|
|
|
x = this.D; // explode the D-register into its fields
|
|
this.Dunit = this.cc.fieldIsolate(x, 3, 5);
|
|
this.DwordCount = this.cc.fieldIsolate(x, 8, 10);
|
|
x = x % 0x40000000; // isolate low-order 30 bits
|
|
this.D18F = (x >>> 29) & 1; // memory inhibit
|
|
this.D21F = (x >>> 26) & 1; // mode
|
|
this.D22F = (x >>> 25) & 1; // direction (for tapes)
|
|
this.D23F = (x >>> 24) & 1; // use word counter
|
|
this.D24F = (x >>> 23) & 1; // write/read
|
|
this.LP = (x >>> 15) & 0x3F; // save control bits for disk, drum, and printer
|
|
this.Daddress = x % 0x8000;
|
|
|
|
this.busyUnit = index = B5500CentralControl.unitIndex[this.D24F & 1][this.Dunit & 0x1F];
|
|
if (this.cc.testUnitBusy(index)) {
|
|
this.D32F = 1; // set unit busy error
|
|
this.finish();
|
|
} else if (!this.cc.testUnitReady(index)) {
|
|
this.D30F = 1; // set unit not-ready error
|
|
this.finish();
|
|
} else {
|
|
this.cc.setUnitBusy(index, 1);
|
|
u = this.cc.unit[index];
|
|
switch(this.Dunit) {
|
|
// disk designates
|
|
case 6:
|
|
case 12:
|
|
this.initiateDiskIO(u);
|
|
break;
|
|
|
|
// printer designates
|
|
case 22:
|
|
case 26:
|
|
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
|
break;
|
|
|
|
// datacom designate
|
|
case 16:
|
|
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
|
break;
|
|
|
|
// card #1 reader/punch
|
|
case 10:
|
|
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
|
break;
|
|
|
|
// card #2 reader
|
|
case 14:
|
|
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
|
break;
|
|
|
|
// SPO designate
|
|
case 30:
|
|
if (this.D24F) {
|
|
u.read(this.makeFinish(this.finishSPORead), this.buffer, 0x7FFF, 0, 0);
|
|
} else {
|
|
chars = this.fetchBufferWithGM(1, 0x7FFF);
|
|
u.write(this.makeFinish(this.finishGeneric), this.buffer, chars, 0, 0);
|
|
}
|
|
break;
|
|
|
|
// magnetic tape designates
|
|
case 1: case 3: case 5: case 7: case 9: case 11: case 13: case 15:
|
|
case 17: case 19: case 21: case 23: case 25: case 27: case 29: case 31:
|
|
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
|
break;
|
|
|
|
// drum designates
|
|
case 4:
|
|
case 8:
|
|
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
|
break;
|
|
|
|
// paper tape designates
|
|
case 18:
|
|
case 20:
|
|
this.D30F = 1; this.finish(); // >>> temp until implemented <<<
|
|
break;
|
|
|
|
// illegal designates
|
|
default:
|
|
this.D30F = 1; // report invalid unit as not ready
|
|
this.finish();
|
|
break;
|
|
} // switch this.Dunit
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500IOUnit.prototype.initiate = function() {
|
|
/* Initiates an I/O operation on this I/O Unit. When P1 executes an IIO instruction,
|
|
it calls the CentralControl.initiateIO() function, which selects an idle I/O Unit and
|
|
calls this function for that unit. Thus, at entry we are still running on top of the
|
|
processor. This routine merely fetches the IOD from memory and then schedules forkIO()
|
|
to run asynchronously. Then we exit back through CC and into P1, thus allowing the
|
|
actual I/O operation to run asynchronously from the processor. Of course, in a browser
|
|
environment, all of the Javascript action occurs on one thread, so this allows us to
|
|
multiplex what are supposed to be asynchronous operations on that thread */
|
|
var that = this; // Establish object context for the callback
|
|
|
|
this.clearD();
|
|
this.AOFF = 0;
|
|
this.EXNF = 0;
|
|
this.D31F = 1; // preset IOD fetch error condition (cleared if successful)
|
|
if (this.fetch(0x08)) { // fetch the IOD address from @10
|
|
this.finish();
|
|
} else {
|
|
this.EXNF = 1;
|
|
this.Daddress = this.W % 0x8000;
|
|
if (this.fetch(this.Daddress)) {// fetch the IOD from that address
|
|
this.finish();
|
|
} else {
|
|
this.D31F = 0; // reset the IOD-fetch error condition
|
|
this.D = this.W;
|
|
this.forkHandle = setTimeout(function() {that.forkIO()}, 0);
|
|
}
|
|
}
|
|
};
|