diff --git a/README.md b/README.md index c9f2169..777a17d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,35 @@ -# retro-b5500 -Automatically exported from code.google.com/p/retro-b5500 +The Burroughs B5500 was an innovative computer system. Released first as the B5000 in 1962 and then, with minor improvements and a new disk subsystem, re-released as the B5500 in 1964, its design was a radical departure from other commercial systems of the day. Many of the concepts that it embodied were being worked on and implemented by others around the same time, but it is difficult to think of another system that pulled so many concepts together and made them work so well in a commercially-successful product: -This project is presently an experimental export from Google Code while we evaluate GitHub as a new home for the retro-b5500 emulator project. + * Multi-programming (multiple tasks sharing the same processor) + * Multi-processing (multiple physical processors sharing common memory and I/O) + * Automatic memory address relocation + * Automatic memory segment overlay (what we now call virtual memory) + * Variable-length memory segments + * Hardware bounds checking + * Stack- and descriptor-oriented instruction set + * Unified integer/floating-point numeric format + * Management by a sophisticated operating system, the Master Control Program, or **MCP** + * Designed for and programmed exclusively in higher-level languages -The project remains hosted at https://code.google.com/p/retro-b5500. -We recommend that you not clone or fork from this repo during this evaluation period. +The B5500 was the foundation for the Burroughs B6000/7000/A Series, which are still produced and sold today as Unisys ClearPath MCP systems. + +The main goal of this project is creation of a web browser-based emulator for the B5500. A second goal is reconstruction of the source and object code for the system. + +A complete software release (Mark XIII, 1971) is presently available from the hosting site below under liberal terms of a Unisys educational/hobbyist license. + +The contents of this project are licensed under the [MIT License](http://www.opensource.org/licenses/mit-license.php). + +| Related Sites | URL | +| ------------- | ----- | +| Getting Started | http://www.phkimpel.us/B5500/webSite/HelpMenu.html | +| Project Blog | http://retro-b5500.blogspot.com/ | +| Emulator hosting site | http://www.phkimpel.us/B5500/ | +| Burroughs Mark XIII Software Release | http://www.phkimpel.us/B5500/webSite/SoftwareRequest.html | +| B5500 at retroComputingTasmania | http://www.retrocomputingtasmania.com/home/projects/burroughs-b5500 | +| Documents at bitsavers.org | http://bitsavers.org/pdf/burroughs/B5000_5500_5700/ | +| Release Downloads | https://drive.google.com/folderview?id=0BxqKm7v4xBswM29qUkxPTkVfYzg&usp=sharing | +| Web/email Forum | http://groups.google.com/group/retro-b5500 | + + +This project was originally hosted on Google Code at https://code.google.com/p/retro-b5500 and moved to GitHub in June 2015. diff --git a/emulator/B5500CentralControl.js b/emulator/B5500CentralControl.js index 031e349..05b1a34 100644 --- a/emulator/B5500CentralControl.js +++ b/emulator/B5500CentralControl.js @@ -61,7 +61,7 @@ function B5500CentralControl(global) { /**************************************/ /* Global constants */ -B5500CentralControl.version = "1.01"; +B5500CentralControl.version = "1.02"; B5500CentralControl.memReadCycles = 2; // assume 2 µs memory read cycle time (the other option was 3 µs) B5500CentralControl.memWriteCycles = 4; // assume 4 µs memory write cycle time (the other option was 6 µs) @@ -896,6 +896,7 @@ B5500CentralControl.prototype.runTest = function runTest(runAddr) { this.P1.start(); }; +/**************************************/ B5500CentralControl.prototype.dumpSystemState = function dumpSystemState(caption, writer) { /* Generates a dump of the processor states and all of memory "caption is an identifying string that is output in the heading line. @@ -961,7 +962,7 @@ B5500CentralControl.prototype.dumpSystemState = function dumpSystemState(caption } } - function convertWordtoANSI(value) { + function convertWordToANSI(value) { /* Converts the "value" as a B5500 word to an eight character string and returns it */ var c; // current character var s = ""; // working string value @@ -1020,7 +1021,7 @@ B5500CentralControl.prototype.dumpSystemState = function dumpSystemState(caption bic += "????????"; } else { line += " " + padOctal(accessor.word, 16); - bic += convertWordtoANSI(accessor.word); + bic += convertWordToANSI(accessor.word); } } // for x @@ -1040,6 +1041,102 @@ B5500CentralControl.prototype.dumpSystemState = function dumpSystemState(caption writer(-1, null); }; +/**************************************/ +B5500CentralControl.prototype.dumpSystemTape = function dumpSystemTape(caption, writer) { + /* Generates a dump of the processor states and all of memory for generation of a tape image. + "caption is an identifying string that is output in the heading line. + "writer" is a function that is called to output lines of text to the outside + world. It takes two parameters: + "phase" is a numeric code indicating the type of line being output: + 0 = initialization (text parameter not valid) + 32 = core memory: control word plus 512 memory words translated to ANSI + -1 = end of dump (text parameter is caption) + "text" is the line of text to be output. + */ + var addr; + var bic; + var dupCount = 0; + var lastLine = ""; + var line; + var lineAddr; + var mod; + var x; + + var accessor = { // Memory access control block + requestorID: "C", // 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 + }; + + var BICtoANSI = [ + "0", "1", "2", "3", "4", "5", "6", "7", + "8", "9", "#", "@", "?", ":", ">", "}", + "+", "A", "B", "C", "D", "E", "F", "G", + "H", "I", ".", "[", "&", "(", "<", "~", + "|", "J", "K", "L", "M", "N", "O", "P", + "Q", "R", "$", "*", "-", ")", ";", "{", + " ", "/", "S", "T", "U", "V", "W", "X", + "Y", "Z", ",", "%", "!", "=", "]", "\""]; + + function convertWordToANSI(value) { + /* Converts the "value" as a B5500 word to an eight character string and returns it */ + var c; // current character + var s = ""; // working string value + var w = value; // working word value + var x; // character counter + + for (x=0; x<8; ++x) { + c = w % 64; + w = (w-c)/64; + s = BICtoANSI[c] + s; + } + return s; + } + + writer(0, null); + + // Dump all of memory + for (mod=0; mod<0x8000; mod+=0x1000) { + accessor.addr = mod; + this.fetch(accessor); + if (accessor.MAED) { // invalid address + bic = convertWordToANSI(0x200000000000 + mod); + for (addr=0; addr<512; ++addr) { + bic += "00000000"; + } + writer(32, bic); + } else { + for (addr=0; addr<0x1000; addr+=512) { + lineAddr = mod+addr; + bic = convertWordToANSI(lineAddr); + for (x=0; x<512; ++x) { + accessor.addr = lineAddr+x; + this.fetch(accessor); + if (accessor.MPED) { + bic += "????????"; + } else { + bic += convertWordToANSI(accessor.word); + } + } // for x + + writer(32, bic); + } // for addr + } + } // for mod + + bic = caption.toUpperCase(); + while (bic.length < 150) { + bic += " "; + } + while (bic.length < 160) { + bic += " "; + } + writer(-1, bic); +}; + /**************************************/ B5500CentralControl.prototype.configureSystem = function configureSystem(cfg) { /* Establishes the hardware module configuration from the system configuration @@ -1126,6 +1223,7 @@ B5500CentralControl.prototype.configureSystem = function configureSystem(cfg) { } // Configure the peripheral units + this.unitStatusMask = 0; for (mnem in cfg.units) { if (cfg.units[mnem].enabled) { specs = B5500CentralControl.unitSpecs[mnem]; @@ -1173,6 +1271,7 @@ B5500CentralControl.prototype.powerOff = function powerOff() { for (x=0; xHelp & Getting Started
A menu of information resources to assist you in setting up and operating the emulator. -
  • Open Source Project -
    Source code, documentation, and other developer resources for the retro-B5500 emulator project at Google Code. +
  • Open Source Project +
    Source code, documentation, and other developer resources for the retro-B5500 emulator project at GitHub.
  • Project Blog
    The retro-B5500 project blog. @@ -62,7 +62,7 @@ Copyright (c) 2013, Nigel Williams and Paul Kimpel • Licensed under the MIT License
    Revised - 2015-02-08 + 2015-06-10
    diff --git a/tools/B5500DiskDirFixer.html b/tools/B5500DiskDirFixer.html index 2228acf..caa3b00 100644 --- a/tools/B5500DiskDirFixer.html +++ b/tools/B5500DiskDirFixer.html @@ -24,6 +24,8 @@ ************************************************************************ * 2013-07-27 P.Kimpel * Original version, from B5500DiskDirList.html. +* 2015-04-17 P.Kimpel +* Add "db=" URL parameter. ***********************************************************************/ "use strict"; @@ -34,7 +36,6 @@ if (!window.indexedDB) { // for Safari, mostly window.addEventListener("load", function() { var configName = "CONFIG"; // database configuration store name var dbName = "B5500DiskUnit"; // IDB database name - var dbVersion; // current IDB database version (leave undefined) var directoryTop; // start of directory area var directoryEnd; // end of directory area var euPrefix = "EU"; // prefix for EU object store names @@ -509,15 +510,15 @@ window.addEventListener("load", function() { } /**************************************/ - function openDatabase(name, version, successor) { - /* Attempts to open the disk subsystem database for the specified "name" - and "version". Stores the IDB database object in "disk" if successful, or - stores null if unsuccessful. Also gets directoryTop from seg 0 */ + function openDatabase(name, successor) { + /* Attempts to open the disk subsystem database for the specified "name". + Stores the IDB database object in "disk" if successful, or stores null + if unsuccessful. Also gets directoryTop from seg 0 */ var block = new Array(30); var db = null; var req; - req = window.indexedDB.open(name, version); + req = window.indexedDB.open(name); // open current version req.onerror = function(ev) { alert("Cannot open disk database: " + ev.target.error); @@ -544,6 +545,32 @@ window.addEventListener("load", function() { } + /**************************************/ + function getDBName(defaultName) { + /* Parses the URL query string for a "db=name" parameter. If "db" is + found, returns the corresponding name; if not found, returns "defaultName" */ + var args; + var i; + var name; + var search = location.search.substring(1); // drop the "?" + var value = defaultName; + var x; + + args = search.split("&"); + for (x=args.length-1; x>=0; --x) { + i = args[x].indexOf("="); + if (i > 0 ) { + name = decodeURIComponent(args[x].substring(0, i)); + if (name.toLowerCase() == "db") { + value = decodeURIComponent(args[x].substring(i+1)); + break; // out of for loop + } + } + } + + return value; + } + /**************************************/ function checkBrowser() { /* Checks whether this browser can support the necessary stuff */ @@ -569,7 +596,8 @@ window.addEventListener("load", function() { /********** Start of window.onload() **********/ if (!checkBrowser()) { - openDatabase(dbName, dbVersion, function() { + dbName = getDBName(dbName); + openDatabase(dbName, function() { directorySearch("TAPE", "COMPARE", fixIt); }); } diff --git a/tools/B5500DiskDirList.html b/tools/B5500DiskDirList.html index 1f5da19..53cb3a6 100644 --- a/tools/B5500DiskDirList.html +++ b/tools/B5500DiskDirList.html @@ -22,6 +22,8 @@ ************************************************************************ * 2013-04-16 P.Kimpel * Original version, from B5500ColdLoader.html. +* 2015-04-17 P.Kimpel +* Add "db=" URL parameter. ***********************************************************************/ "use strict"; @@ -32,7 +34,6 @@ if (!window.indexedDB) { // for Safari, mostly window.addEventListener("load", function() { var configName = "CONFIG"; // database configuration store name var dbName = "B5500DiskUnit"; // IDB database name - var dbVersion; // current IDB database version (leave undefined) var directoryTop; // start of directory area var directoryEnd; // end of directory area var euPrefix = "EU"; // prefix for EU object store names @@ -505,15 +506,15 @@ window.addEventListener("load", function() { } /**************************************/ - function openDatabase(name, version, successor) { - /* Attempts to open the disk subsystem database for the specified "name" - and "version". Stores the IDB database object in "disk" if successful, or - stores null if unsuccessful. Also gets directoryTop from seg 0 */ + function openDatabase(name, successor) { + /* Attempts to open the disk subsystem database for the specified "name". + Stores the IDB database object in "disk" if successful, or stores null + if unsuccessful. Also gets directoryTop from seg 0 */ var block = new Array(30); var db = null; var req; - req = window.indexedDB.open(name, version); + req = window.indexedDB.open(name); // open current version req.onerror = function(ev) { alert("Cannot open disk database: " + ev.target.error); @@ -540,6 +541,32 @@ window.addEventListener("load", function() { } + /**************************************/ + function getDBName(defaultName) { + /* Parses the URL query string for a "db=name" parameter. If "db" is + found, returns the corresponding name; if not found, returns "defaultName" */ + var args; + var i; + var name; + var search = location.search.substring(1); // drop the "?" + var value = defaultName; + var x; + + args = search.split("&"); + for (x=args.length-1; x>=0; --x) { + i = args[x].indexOf("="); + if (i > 0 ) { + name = decodeURIComponent(args[x].substring(0, i)); + if (name.toLowerCase() == "db") { + value = decodeURIComponent(args[x].substring(i+1)); + break; // out of for loop + } + } + } + + return value; + } + /**************************************/ function checkBrowser() { /* Checks whether this browser can support the necessary stuff */ @@ -565,7 +592,8 @@ window.addEventListener("load", function() { /********** Start of window.onload() **********/ if (!checkBrowser()) { - openDatabase(dbName, dbVersion, function() { + dbName = getDBName(dbName); + openDatabase(dbName, function() { directoryList(function() {}); }); } diff --git a/tools/B5500DiskFileList.html b/tools/B5500DiskFileList.html index f29109a..954e9b1 100644 --- a/tools/B5500DiskFileList.html +++ b/tools/B5500DiskFileList.html @@ -23,6 +23,8 @@ ************************************************************************ * 2013-04-17 P.Kimpel * Original version, from B5500DiskDirList.html. +* 2015-04-17 P.Kimpel +* Add "db=" URL parameter. ***********************************************************************/ "use strict"; @@ -33,7 +35,6 @@ if (!window.indexedDB) { // for Safari, mostly window.addEventListener("load", function() { var configName = "CONFIG"; // database configuration store name var dbName = "B5500DiskUnit"; // IDB database name - var dbVersion; // current IDB database version (leave undefined) var directoryTop; // start of directory area var directoryEnd; // end of directory area var euPrefix = "EU"; // prefix for EU object store names @@ -653,15 +654,15 @@ window.addEventListener("load", function() { } /**************************************/ - function openDatabase(name, version, successor) { - /* Attempts to open the disk subsystem database for the specified "name" - and "version". Stores the IDB database object in "disk" if successful, or - stores null if unsuccessful. Also gets directoryTop from seg 0 */ + function openDatabase(name, successor) { + /* Attempts to open the disk subsystem database for the specified "name". + Stores the IDB database object in "disk" if successful, or stores null + if unsuccessful. Also gets directoryTop from seg 0 */ var block = new Array(30); var db = null; var req; - req = window.indexedDB.open(name, version); + req = window.indexedDB.open(name); // open current version req.onerror = function(ev) { alert("Cannot open disk database: " + ev.target.error); @@ -688,6 +689,32 @@ window.addEventListener("load", function() { } + /**************************************/ + function getDBName(defaultName) { + /* Parses the URL query string for a "db=name" parameter. If "db" is + found, returns the corresponding name; if not found, returns "defaultName" */ + var args; + var i; + var name; + var search = location.search.substring(1); // drop the "?" + var value = defaultName; + var x; + + args = search.split("&"); + for (x=args.length-1; x>=0; --x) { + i = args[x].indexOf("="); + if (i > 0 ) { + name = decodeURIComponent(args[x].substring(0, i)); + if (name.toLowerCase() == "db") { + value = decodeURIComponent(args[x].substring(i+1)); + break; // out of for loop + } + } + } + + return value; + } + /**************************************/ function checkBrowser() { /* Checks whether this browser can support the necessary stuff */ @@ -713,7 +740,8 @@ window.addEventListener("load", function() { /********** Start of window.onload() **********/ if (!checkBrowser()) { - openDatabase(dbName, dbVersion, function() { + dbName = getDBName(dbName); + openDatabase(dbName, function() { directoryList(function() {}); }); } diff --git a/tools/B5500DiskSystemLogFixer.html b/tools/B5500DiskSystemLogFixer.html index bcc513c..53f5c7c 100644 --- a/tools/B5500DiskSystemLogFixer.html +++ b/tools/B5500DiskSystemLogFixer.html @@ -24,6 +24,8 @@ ************************************************************************ * 2013-07-27 P.Kimpel * Original version, from B5500DiskDirList.html. +* 2015-04-17 P.Kimpel +* Add "db=" URL parameter. ***********************************************************************/ "use strict"; @@ -34,7 +36,6 @@ if (!window.indexedDB) { // for Safari, mostly window.addEventListener("load", function() { var configName = "CONFIG"; // database configuration store name var dbName = "B5500DiskUnit"; // IDB database name - var dbVersion; // current IDB database version (leave undefined) var directoryTop; // start of directory area var directoryEnd; // end of directory area var euPrefix = "EU"; // prefix for EU object store names @@ -495,15 +496,15 @@ window.addEventListener("load", function() { } /**************************************/ - function openDatabase(name, version, successor) { - /* Attempts to open the disk subsystem database for the specified "name" - and "version". Stores the IDB database object in "disk" if successful, or - stores null if unsuccessful. Also gets directoryTop from seg 0 */ + function openDatabase(name, successor) { + /* Attempts to open the disk subsystem database for the specified "name". + Stores the IDB database object in "disk" if successful, or stores null + if unsuccessful. Also gets directoryTop from seg 0 */ var block = new Array(30); var db = null; var req; - req = window.indexedDB.open(name, version); + req = window.indexedDB.open(name); // open current version req.onerror = function(ev) { alert("Cannot open disk database: " + ev.target.error); @@ -530,6 +531,32 @@ window.addEventListener("load", function() { } + /**************************************/ + function getDBName(defaultName) { + /* Parses the URL query string for a "db=name" parameter. If "db" is + found, returns the corresponding name; if not found, returns "defaultName" */ + var args; + var i; + var name; + var search = location.search.substring(1); // drop the "?" + var value = defaultName; + var x; + + args = search.split("&"); + for (x=args.length-1; x>=0; --x) { + i = args[x].indexOf("="); + if (i > 0 ) { + name = decodeURIComponent(args[x].substring(0, i)); + if (name.toLowerCase() == "db") { + value = decodeURIComponent(args[x].substring(i+1)); + break; // out of for loop + } + } + } + + return value; + } + /**************************************/ function checkBrowser() { /* Checks whether this browser can support the necessary stuff */ @@ -555,7 +582,8 @@ window.addEventListener("load", function() { /********** Start of window.onload() **********/ if (!checkBrowser()) { - openDatabase(dbName, dbVersion, function() { + dbName = getDBName(dbName); + openDatabase(dbName, function() { directorySearch("SYSTEM", "LOG", fixIt); }); } diff --git a/tools/B5500FixStorageNames.html b/tools/B5500FixStorageNames.html index b933b78..79e9757 100644 --- a/tools/B5500FixStorageNames.html +++ b/tools/B5500FixStorageNames.html @@ -2,7 +2,10 @@ B5500 Emulator Fix StorageNames - + @@ -14,6 +17,36 @@ var storageName = "B5500DiskUnit"; window.addEventListener("load", function(ev) { var req; + /**************************************/ + function getDBName(defaultName) { + /* Parses the URL query string for a "db=name" parameter. If "db" is + found, returns the corresponding name; if not found, returns "defaultName" */ + var args; + var i; + var name; + var search = location.search.substring(1); // drop the "?" + var value = defaultName; + var x; + + args = search.split("&"); + for (x=args.length-1; x>=0; --x) { + i = args[x].indexOf("="); + if (i > 0 ) { + name = decodeURIComponent(args[x].substring(0, i)); + if (name.toLowerCase() == "db") { + value = decodeURIComponent(args[x].substring(i+1)); + break; // out of for loop + } + } + } + + return value; + } + + /***************************************/ + + storageName = getDBName(storageName); + req = window.indexedDB.open(configName); req.onerror = function(ev) { diff --git a/tools/COLDSTART-XIII.card b/tools/COLDSTART-XIII.card index 3ba3228..a0e003a 100644 --- a/tools/COLDSTART-XIII.card +++ b/tools/COLDSTART-XIII.card @@ -90,8 +90,8 @@ 00000000=000000#04*)H/}VY6044A4("000000:2*14"$"50R004J0O4J70100S0|JI000001000011 DRCTRYTP 2000 % START OF DIRECTORY AREA DIRECT 3604 % END OF DIRECTORY AREA -ESU 3 % MAX NUMBER OF EUS -DATE 09/01/84 % CURRENT SYSTEM DATE +ESU 2 % MAX NUMBER OF EUS +DATE 05/10/85 % CURRENT SYSTEM DATE SYSTEMS = 1 % MUST BE 1 (FOR NOW) FENCE = 16384 % USED BY TSMCP ONLY FILE DIRCTRY/DISK, 1|1600, 999 % DIRECTORY ENTRY FOR DIRECTORY @@ -101,13 +101,13 @@ FILE SYSTEM/LOG, 1|5000, 999 % RESERVE SPACE FOR LOG FILE MCP/DISK, 1|1500, 999 % RESERVE SPACE FOR MCP CODE 8610 FILE DMPAREA/DISK, 1|100, 999 % RESERVE SPACE FOR MEM DUMP - 10108 + 10110 FILE RESERVE/DISK, 1|2000, 999 % RESERVE FOR NO-USER-DISK EVENT - 10208 -TYPE BOJ % PRINT BOJ MESSAGES -TYPE EOJ % PRINT EOJ MESSAGES + 10210 +TYPE BOJ % PRINT BOJ MESSAGES +TYPE EOJ % PRINT EOJ MESSAGES TYPE OPEN % PRINT FILE OPEN MESSAGES -USE TERMNATE % REMOVE TASKS FROM MEMORY AT EOJ (NORMALLY SET) +USE TERMNATE % REMOVE TASKS FROM MEMORY AT EOJ (NORMALLY SET) TYPE TIME % REQUIRE TIME TO BE ENTERED AT SPO AFTER HALT/LOAD USE ONEBREAK % WRITE ALL BREAKOUTS TO A SINGLE TAPE USE AUTOPRNT % PRINT BACKUP (SPOOLED) FILES BY DEFAULT @@ -243,4 +243,4 @@ STOP ,FORTRAN/DISK - ,COBOL/DISK - ,LOGOUT/DISK -?END \ No newline at end of file +?END \ No newline at end of file diff --git a/webSite/HelpMenu.html b/webSite/HelpMenu.html index dec5e39..0f07f15 100644 --- a/webSite/HelpMenu.html +++ b/webSite/HelpMenu.html @@ -21,37 +21,37 @@

    Main Links

    @@ -63,7 +63,7 @@ Copyright (c) 2013, Nigel Williams and Paul Kimpel • Licensed under the MIT License
    Revised - 2014-09-26 + 2015-06-10
    diff --git a/webUI/B5500CardPunch.js b/webUI/B5500CardPunch.js index c0889df..0cf51df 100644 --- a/webUI/B5500CardPunch.js +++ b/webUI/B5500CardPunch.js @@ -71,6 +71,40 @@ B5500CardPunch.prototype.clear = function clear() { this.stacker2Count = 0; // cards in stacker #2 }; +/**************************************/ +B5500CardPunch.prototype.emptyStacker = function emptyStacker(stacker) { + /* Empties the stacker of all text lines */ + + while (stacker.firstChild) { + stacker.removeChild(stacker.firstChild); + } +}; + +/**************************************/ +B5500CardPunch.prototype.copyStacker = function copyStacker(ev) { + /* Copies the text contents of a "stacker" area of the device, opens a new + temporary window, and pastes that text into the window so it can be copied + or saved by the user */ + var stacker = ev.target; + var text = stacker.textContent; + var title = "B5500 " + this.mnemonic + " Stacker Snapshot"; + var win = window.open("./B5500FramePaper.html", this.mnemonic + "-Snapshot", + "scrollbars,resizable,width=500,height=500"); + + win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2); + win.addEventListener("load", function() { + var doc; + + doc = win.document; + doc.title = title; + doc.getElementById("Paper").textContent = text; + }); + + this.emptyStacker(stacker); + ev.preventDefault(); + ev.stopPropagation(); +}; + /**************************************/ B5500CardPunch.prototype.setPunchReady = function setPunchReady(ready) { /* Controls the ready-state of the card punch */ @@ -86,14 +120,10 @@ B5500CardPunch.prototype.setPunchReady = function setPunchReady(ready) { this.stacker1Count = this.stacker2Count = 0; this.$$("CPStacker1Bar").value = 0; B5500Util.removeClass(this.$$("CPStacker1Full"), "annunciatorLit"); - while (this.stacker1.firstChild) { - this.stacker1.removeChild(this.stacker1.firstChild); - } + this.emptyStacker(stacker1); this.$$("CPStacker2Bar").value = 0; B5500Util.removeClass(this.$$("CPStacker2Full"), "annunciatorLit"); - while (this.stacker2.firstChild) { - this.stacker2.removeChild(this.stacker2.firstChild); - } + this.emptyStacker(stacker2); } } this.armRunout(false); @@ -214,6 +244,10 @@ B5500CardPunch.prototype.punchOnload = function punchOnload() { this.window.addEventListener("beforeunload", B5500CardPunch.prototype.beforeUnload, false); + this.stacker1.addEventListener("dblclick", + B5500CentralControl.bindMethod(this, B5500CardPunch.prototype.copyStacker)); + this.stacker2.addEventListener("dblclick", + B5500CentralControl.bindMethod(this, B5500CardPunch.prototype.copyStacker)); this.$$("CPStartBtn").addEventListener("click", B5500CentralControl.bindMethod(this, B5500CardPunch.prototype.CPStartBtn_onclick), false); this.$$("CPStopBtn").addEventListener("click", diff --git a/webUI/B5500ColdLoader.html b/webUI/B5500ColdLoader.html index 15492c8..c4e27ac 100644 --- a/webUI/B5500ColdLoader.html +++ b/webUI/B5500ColdLoader.html @@ -72,7 +72,6 @@ if (!window.indexedDB) { // for Safari, mostly window.addEventListener("load", function() { var configName = "CONFIG"; // database configuration store name var dbName = "B5500DiskUnit"; // IDB database name - var dbVersion = 1; // current IDB database version var directoryTop = 2000; // start of directory area var directoryEnd = 3008; // end of directory area var euSize = 200000; // model I size (5 Storage Units: 6MW or 48MC) @@ -1989,13 +1988,13 @@ window.addEventListener("load", function() { } /**************************************/ - function openDatabase(name, version) { - /* Attempts to open the disk subsystem database for the specified "name" - and "version". Stores the IDB database object in "db" if successful, or - stores null if unsuccessful */ + function openDatabase(name) { + /* Attempts to open the disk subsystem database for the specified "name". + Stores the IDB database object in "db" if successful, or stores null + if unsuccessful */ var req; - req = window.indexedDB.open(name, version); + req = window.indexedDB.open(name); req.onerror = function(ev) { alert("Cannot open retro-B5500 Disk Subsystem database:\n" + ev.target.error); @@ -2103,7 +2102,7 @@ window.addEventListener("load", function() { deleteDiskDatabase(dbName); }); $$("LoadBtn").addEventListener("click", loadFromTape); - openDatabase(dbName, dbVersion); + openDatabase(dbName); } }, false); diff --git a/webUI/B5500Console.css b/webUI/B5500Console.css index 56bce97..b7b57a7 100644 --- a/webUI/B5500Console.css +++ b/webUI/B5500Console.css @@ -74,8 +74,9 @@ H1 { border-collapse: collapse; border-spacing: 0} #PageFooter { - position: absolute; - font-size: 75%; - bottom: 4px; - text-align: center; - width: 100%} + margin-top: 2em; + margin-left: calc(50% - 3in); + margin-right: calc(50% - 3in); + font-weight: bold; + color: red; + text-align: center} diff --git a/webUI/B5500Console.html b/webUI/B5500Console.html index 74e713b..968e2c8 100644 --- a/webUI/B5500Console.html +++ b/webUI/B5500Console.html @@ -22,6 +22,8 @@ * Split off Javascript code into a separate script. * 2015-01-24 P.Kimpel * Strip down to new, minimal home page sans console panel. +* 2015-06-10 P.Kimpel +* Change project links from Google Code to GitHub. ***********************************************************************/ --> @@ -63,13 +65,13 @@
    - -
    + Open-Source Project Project Blog
    + Getting Started Wiki   @@ -87,6 +89,10 @@ + + @@ -101,9 +107,5 @@
    - - \ No newline at end of file diff --git a/webUI/B5500ConsolePanel.js b/webUI/B5500ConsolePanel.js index 8395461..c65384b 100644 --- a/webUI/B5500ConsolePanel.js +++ b/webUI/B5500ConsolePanel.js @@ -296,14 +296,14 @@ B5500ConsolePanel.prototype.dumpState = function dumpState(caption) { /* Call-back function for cc.dumpSystemState */ switch (phase) { - case 0: + case 0: // Initialization and heading line lastPhase = phase; doc.writeln(escapeHTML(text)); doc.writeln("User Agent: " + navigator.userAgent); break; - case 1: - case 2: + case 1: // Processor 1 state + case 2: // Processor 2 state if (phase == lastPhase) { doc.writeln(escapeHTML(text)); } else { @@ -314,7 +314,7 @@ B5500ConsolePanel.prototype.dumpState = function dumpState(caption) { } break; - case 32: + case 32: // Memory lines if (phase != lastPhase) { lastPhase = phase; doc.writeln(); @@ -323,7 +323,7 @@ B5500ConsolePanel.prototype.dumpState = function dumpState(caption) { doc.writeln(escapeHTML(text)); break; - case -1: + case -1: // Termination break; } // switch } @@ -341,6 +341,72 @@ B5500ConsolePanel.prototype.dumpState = function dumpState(caption) { win.focus(); } +/**************************************/ +B5500ConsolePanel.prototype.dumpTape = function dumpTape(caption) { + /* Generates a dump of all of memory to a MEMORY/DUMP tape image */ + var doc; + var win = window.open("", "", "location=no,resizable,scrollbars,status"); + var x; + + var htmlMatch = /[<>&"]/g; // regular expression for escaping HTML text + var tapeLabel = " LABEL 0MEMORY 0DUMP00100175001019936500000000000000000000000000000000000000000"; + + function htmlFilter(c) { + /* Used to escape HTML-sensitive characters in a string */ + switch (c) { + case "&": + return "&"; + case "<": + return "<"; + case ">": + return ">"; + case "\"": + return """; + default: + return c; + } + } + + function escapeHTML(text) { + /* Returns "text" as escaped HTML */ + + return text.replace(htmlMatch, htmlFilter); + } + + function writer(phase, text) { + /* Call-back function for cc.dumpSystemTape */ + + switch (phase) { + case 0: // Initialization, write tape label + doc.writeln(tapeLabel); + doc.writeln("}"); // tape mark + break; + + case 32: // Dump data + doc.writeln(escapeHTML(text)); + break; + + case -1: // Termination, write tape label + doc.writeln(text); + doc.writeln("}"); // tape mark + doc.writeln(tapeLabel); + break; + } // switch + } + + doc = win.document; + doc.open(); + doc.writeln("retro-B5500 Console Tape Dump"); + doc.writeln(""); + doc.write("
    ");
    +
    +    this.cc.dumpSystemTape(caption, writer);
    +
    +    doc.writeln("
    ") + doc.close(); + win.focus(); +} + /**************************************/ B5500ConsolePanel.prototype.displayCallbackState = function displayCallbackState() { /* Builds a table of outstanding callback state */ @@ -757,6 +823,10 @@ B5500ConsolePanel.prototype.consoleOnload = function consoleOnload(ev) { B5500CentralControl.bindMethod(this, function(ev) { this.dumpState("Memory-Check Button"); })); + this.$$("NotReadyBtn").addEventListener("click", + B5500CentralControl.bindMethod(this, function(ev) { + this.dumpTape("Not-Ready Button"); + })); this.aControl = this.$$("AControlBtn"); this.aNormal = this.$$("ANormalBtn"); @@ -767,7 +837,8 @@ B5500ConsolePanel.prototype.consoleOnload = function consoleOnload(ev) { this.buildLightMaps(); this.cc = new B5500CentralControl(this.global); - this.global.B5500DumpState = this.dumpState; + this.global.B5500DumpState = this.dumpState; // for use by Processor + this.global.B5500DumpState = this.dumpTape; // for use by Processor this.window.resizeTo(this.doc.documentElement.scrollWidth + this.window.outerWidth - this.window.innerWidth + 2, // kludge +2, dunno why this.doc.documentElement.scrollHeight + this.window.outerHeight - this.window.innerHeight); this.window.moveTo(screen.availWidth - this.window.outerWidth, 0); diff --git a/webUI/B5500DatacomUnit.js b/webUI/B5500DatacomUnit.js index ea01c25..a06c127 100644 --- a/webUI/B5500DatacomUnit.js +++ b/webUI/B5500DatacomUnit.js @@ -273,7 +273,11 @@ B5500DatacomUnit.prototype.outputChar = function outputChar() { var nextTime; var stamp; - if (this.bufIndex < this.bufLength) { + if (this.bufIndex >= this.bufLength) { + this.interrupt = true; + this.setState(this.fullBuffer ? this.bufWriteReady : this.bufIdle); + this.signal(); + } else { stamp = performance.now(); nextTime = (this.nextCharTime < stamp ? stamp : this.nextCharTime) + this.charPeriod; delay = nextTime - stamp; @@ -306,10 +310,6 @@ B5500DatacomUnit.prototype.outputChar = function outputChar() { break; } this.showBufferIndex(); - } else { - this.interrupt = true; - this.setState(this.fullBuffer ? this.bufWriteReady : this.bufIdle); - this.signal(); } }; @@ -370,7 +370,7 @@ B5500DatacomUnit.prototype.keyAction = function keyAction(ev, c) { ev.stopPropagation(); ev.preventDefault(); break; - case 0x21: // ! EOT, disconnect + case 0x21: // !, EOT, send disconnect request this.buffer[this.bufIndex++] = 0x7D; // } greater-or-equal code this.interrupt = true; this.abnormal = true; @@ -400,6 +400,7 @@ B5500DatacomUnit.prototype.keyAction = function keyAction(ev, c) { case 0x0C: // Ctrl-L, FF, clear input buffer if (this.bufState == this.bufInputBusy) { this.bufIndex = this.bufLength = 0; + this.setState(this.bufIdle); } ev.stopPropagation(); ev.preventDefault(); @@ -450,27 +451,7 @@ B5500DatacomUnit.prototype.keyPress = function keyPress(ev) { var c = ev.charCode; if (ev.ctrlKey) { - switch(c) { - case 0x42: - case 0x62: - c = 0x02; // Ctrl-B: force STX, break - break; - case 0x45: - case 0x65: - c = 0x05; // Ctrl-E:force ENQ, WRU - break; - case 0x4C: - case 0x6C: - c = 0x0C; // Ctrl-L: force FF, clear input buffer - break; - case 0x51: - case 0x71: - c = 0x7E; // Ctrl-Q: DC1, X-ON to ~ (GM) for end-of-message - break; - default: - c = 0; // not something we want - break; - } + c = 0; // not something we want } this.keyAction(ev, c); @@ -489,6 +470,31 @@ B5500DatacomUnit.prototype.keyDown = function keyDown(ev) { case 0x0D: // Enter: force ~ (GM) for end-of-message this.keyAction(ev, 0x7E); break; + case 0x42: + if (ev.ctrlKey) { + this.keyAction(ev, 0x02); // Ctrl-B: force STX, break + } + break; + case 0x44: + if (ev.ctrlKey) { + this.keyAction(ev, 0x21); // Ctrl-D: force EOT, disconnect request + } + break; + case 0x45: + if (ev.ctrlKey) { + this.keyAction(ev, 0x05); // Ctrl-E:force ENQ, WRU + } + break; + case 0x4C: + if (ev.ctrlKey) { + this.keyAction(ev, 0x0C); // Ctrl-L: force FF, clear input buffer + } + break; + case 0x51: + if (ev.ctrlKey) { + this.keyAction(ev, 0x7E); // Ctrl-Q: DC1, X-ON to ~ (GM) for end-of-message + } + break; } }; @@ -505,10 +511,10 @@ B5500DatacomUnit.prototype.termConnectBtnClick = function termConnectBtnClick(ev }; /**************************************/ -B5500SPOUnit.prototype.copyPaper = function copyPaper(ev) { +B5500DatacomUnit.prototype.copyPaper = function copyPaper(ev) { /* Copies the text contents of the "paper" area of the SPO, opens a new temporary window, and pastes that text into the window so it can be copied - or saved */ + or saved by the user */ var text = ev.target.textContent; var title = "B5500 " + this.mnemonic + " Text Snapshot"; var win = window.open("./B5500FramePaper.html", this.mnemonic + "-Snapshot", @@ -566,7 +572,7 @@ B5500DatacomUnit.prototype.datacomOnload = function datacomOnload() { this.$$("TermOut").addEventListener("keypress", B5500CentralControl.bindMethod(this, B5500DatacomUnit.prototype.keyPress), false); this.paper.addEventListener("dblclick", - B5500CentralControl.bindMethod(this, B5500SPOUnit.prototype.copyPaper), false); + B5500CentralControl.bindMethod(this, B5500DatacomUnit.prototype.copyPaper), false); this.$$("TermConnectBtn").addEventListener("click", B5500CentralControl.bindMethod(this, B5500DatacomUnit.prototype.termConnectBtnClick), false); diff --git a/webUI/B5500DiskStorageConfig.js b/webUI/B5500DiskStorageConfig.js index b61d50c..b10d8c7 100644 --- a/webUI/B5500DiskStorageConfig.js +++ b/webUI/B5500DiskStorageConfig.js @@ -463,7 +463,7 @@ B5500DiskStorageConfig.prototype.normalizeStorageConfig = function normalizeStor if (newConfig.configLevel != this.dbConfigLevel) { this.alertWin.alert("ERROR: Cannot normalize existing CONFIG\nlevel " + - newConfig.configLevel + " to current level " + that.dbConfigLevel); + newConfig.configLevel + " to current level " + this.dbConfigLevel); } return newConfig; @@ -695,6 +695,7 @@ B5500DiskStorageConfig.prototype.saveStorageDialog = function saveStorageDialog( /**************************************/ B5500DiskStorageConfig.prototype.deleteStorageDialog = function deleteStorageDialog(storageName) { /* Initiates deletion of the currently-selected system configuration */ + var that = this; function deleteFailed(ev) { that.alertWin.alert("Deletion of database \"" + storageName + diff --git a/webUI/B5500DiskUnit.js b/webUI/B5500DiskUnit.js index 6632ca4..b77220b 100644 --- a/webUI/B5500DiskUnit.js +++ b/webUI/B5500DiskUnit.js @@ -134,8 +134,8 @@ function B5500DiskUnit(mnemonic, index, designate, statusChange, signal, options this.initiateStamp = 0; // timestamp of last initiation (set by IOUnit) this.config = null; // copy of CONFIG store contents this.db = null; // the IDB database object - this.euBase = // base EU number for this DFCU - (mnemonic=="DKB" && !options.DFX ? 10 : 0); + this.euPrefix = // prefix for EU object store names + (mnemonic=="DKA" || options.DFX ? "EU" : "EU1"); this.stdFinish = B5500CentralControl.bindMethod(this, B5500DiskUnit.prototype.stdFinish); @@ -143,7 +143,6 @@ function B5500DiskUnit(mnemonic, index, designate, statusChange, signal, options this.openDatabase(); // attempt to open the IDB database } -B5500DiskUnit.prototype.euPrefix = "EU"; // prefix for EU object store names B5500DiskUnit.prototype.charXferRate = 96; // avg. transfer rate [characters/ms = KC/sec] B5500DiskUnit.prototype.modelILatency = 40; // Model-I disk max rotational latency [ms] B5500DiskUnit.prototype.modelIBLatency = 80; // Model-IB disk max rotational latency [ms] @@ -203,7 +202,7 @@ B5500DiskUnit.prototype.loadStorageConfig = function loadStorageConfig(storageCo var name; for (name in config) { // for each property in the config - if (name.search(euRex) == 0) { // filter name for "EUn" or "EUnn" + if (name.search(euRex) == 0) { // filter name for "EUn" or "EU1n" eu = config[name]; eu.maxLatency = (eu.slow ? this.modelIBLatency : this.modelILatency); eu.charXferRate = this.charXferRate; @@ -278,7 +277,7 @@ B5500DiskUnit.prototype.read = function read(finish, buffer, length, mode, contr this.finish = finish; // for global error handler var segs = Math.floor((length+239)/240); var segAddr = control % 1000000; // starting seg address - var euNumber = (control % 10000000 - segAddr)/1000000 + this.euBase; + var euNumber = (control % 10000000 - segAddr)/1000000; var euName = this.euPrefix + euNumber; var endAddr = segAddr+segs-1; // ending seg address @@ -367,14 +366,16 @@ B5500DiskUnit.prototype.write = function write(finish, buffer, length, mode, con this.finish = finish; // for global error handler var segs = Math.floor((length+239)/240); var segAddr = control % 1000000; // starting seg address - var euNumber = (control % 10000000 - segAddr)/1000000 + this.euBase; + var euNumber = (control % 10000000 - segAddr)/1000000; var euName = this.euPrefix + euNumber; var endAddr = segAddr+segs-1; // ending seg address eu = this.config[euName]; if (!eu) { // EU does not exist + console.log(euName + " does not exist"); this.stdFinish(0x20, 0); // set D27F for EU not ready } else if (segAddr < 0) { + console.log(euName + " invalid starting addr"); this.stdFinish(0x20, 0); // set D27F for invalid starting seg address } else { if (endAddr >= eu.size) { // if read is past end of disk @@ -392,6 +393,14 @@ B5500DiskUnit.prototype.write = function write(finish, buffer, length, mode, con } else { // Do the write txn = this.db.transaction(euName, "readwrite") + txn.onerror = function writeTxnOnError(ev) { + console.log(euName + " write txn onerror", ev); + that.stdFinish(0x20, 0); + }; + txn.onabort = function writeTxnOnAbort(ev) { + console.log(euName + " write txn onabort", ev); + that.stdFinish(0x20, 0); + }; txn.oncomplete = function writeComplete(ev) { that.timer = setCallback(that.mnemonic, that, finishTime - performance.now(), function writeTimeout() { @@ -440,7 +449,7 @@ B5500DiskUnit.prototype.readCheck = function readCheck(finish, length, control) this.finish = finish; // for global error handler var segs = Math.floor((length+239)/240); var segAddr = control % 1000000; // starting seg address - var euNumber = (control % 10000000 - segAddr)/1000000 + this.euBase; + var euNumber = (control % 10000000 - segAddr)/1000000; var euName = this.euPrefix + euNumber; var endAddr = segAddr+segs-1; // ending seg address @@ -501,7 +510,7 @@ B5500DiskUnit.prototype.readInterrogate = function readInterrogate(finish, contr the address */ var eu; // EU characteristics object var segAddr = control % 1000000; // starting seg address - var euNumber = (control % 10000000 - segAddr)/1000000 + this.euBase; + var euNumber = (control % 10000000 - segAddr)/1000000; var euName = this.euPrefix + euNumber; this.finish = finish; // for global error handler @@ -511,11 +520,13 @@ B5500DiskUnit.prototype.readInterrogate = function readInterrogate(finish, contr } else { if (segAddr < 0 || segAddr >= eu.size) { // if read is past end of disk this.errorMask |= 0x20; // set D27F for invalid seg address + } else if (eu.slow) { + this.errorMask |= 0x10; // set D28F (lockout bit) to indicate Mod IB (slow) disk } this.timer = setCallback(this.mnemonic, this, Math.random()*eu.maxLatency + this.initiateStamp - performance.now(), function readInterrogateTimeout() { - this.stdFinish(0, length); + this.stdFinish(0, 0); }); } }; @@ -533,7 +544,7 @@ B5500DiskUnit.prototype.writeInterrogate = function writeInterrogate(finish, con var eu; // EU characteristics object var segAddr = control % 1000000; // starting seg address - var euNumber = (control % 10000000 - segAddr)/1000000 + this.euBase; + var euNumber = (control % 10000000 - segAddr)/1000000; var euName = this.euPrefix + euNumber; this.finish = finish; // for global error handler @@ -547,7 +558,7 @@ B5500DiskUnit.prototype.writeInterrogate = function writeInterrogate(finish, con this.timer = setCallback(this.mnemonic, this, Math.random()*eu.maxLatency + this.initiateStamp - performance.now(), function writeInterrogateTimeout() { - this.stdFinish(0, length); + this.stdFinish(0, 0); }); } }; diff --git a/webUI/B5500LinePrinter.html b/webUI/B5500LinePrinter.html index cb3a67a..150d860 100644 --- a/webUI/B5500LinePrinter.html +++ b/webUI/B5500LinePrinter.html @@ -25,7 +25,7 @@ - + diff --git a/webUI/B5500LinePrinter.js b/webUI/B5500LinePrinter.js index 715cd00..35c6937 100644 --- a/webUI/B5500LinePrinter.js +++ b/webUI/B5500LinePrinter.js @@ -83,7 +83,6 @@ B5500LinePrinter.prototype.clear = function clear() { B5500LinePrinter.prototype.setPrinterReady = function setPrinterReady(ready) { /* Controls the ready-state of the line printer */ - this.formFeedCount = 0; if (ready && !this.ready) { this.statusChange(1); B5500Util.addClass(this.$$("LPStartBtn"), "greenLit") @@ -102,15 +101,43 @@ B5500LinePrinter.prototype.ripPaper = function ripPaper(ev) { /* Handles an event to clear the "paper" from the printer */ this.formFeedCount = 0; - if (this.window.confirm("Do you want to clear the \"paper\" from the printer?")) { - B5500Util.removeClass(this.$$("LPEndOfPaperBtn"), "whiteLit"); - this.paperMeter.value = this.paperLeft = this.maxPaperLines; - while (this.paper.firstChild) { - this.paper.removeChild(this.paper.firstChild); - } + B5500Util.removeClass(this.$$("LPEndOfPaperBtn"), "whiteLit"); + this.paperMeter.value = this.paperLeft = this.maxPaperLines; + while (this.paper.firstChild) { + this.paper.removeChild(this.paper.firstChild); } }; +/**************************************/ +B5500LinePrinter.prototype.copyPaper = function copyPaper(ev) { + /* Copies the text contents of the "paper" area of the device, opens a new + temporary window, and pastes that text into the window so it can be copied + or saved by the user */ + var barGroup = this.paper.firstChild; + var text = ""; + var title = "B5500 " + this.mnemonic + " Paper Snapshot"; + var win = window.open("./B5500FramePaper.html", this.mnemonic + "-Snapshot", + "scrollbars,resizable,width=500,height=500"); + + while (barGroup) { + text += barGroup.textContent + "\n"; + barGroup = barGroup.nextSibling; + } + + win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2); + win.addEventListener("load", function() { + var doc; + + doc = win.document; + doc.title = title; + doc.getElementById("Paper").textContent = text; + }); + + this.ripPaper(); + ev.preventDefault(); + ev.stopPropagation(); +}; + /**************************************/ B5500LinePrinter.prototype.appendLine = function appendLine(text) { /* Appends one line, with a trailing new-line character, to the current @@ -147,7 +174,7 @@ B5500LinePrinter.prototype.appendLine = function appendLine(text) { /**************************************/ B5500LinePrinter.prototype.printLine = function printLine(text, control) { /* Prints one line to the "paper", handling carriage control and greenbar - group completion. For now, SPACE 0 (overprintng) is treated as single-spacing */ + group completion. For now, SPACE 0 (overprinting) is treated as single-spacing */ var lines = 1; this.appendLine(text || "\xA0"); @@ -238,7 +265,7 @@ B5500LinePrinter.prototype.LPStopBtn_onclick = function LPStopBtn_onclick(ev) { /**************************************/ B5500LinePrinter.prototype.LPSpaceBtn_onclick = function LPSpaceBtn_onclick(ev) { - /* Handle the click event for the Skip To Heading button */ + /* Handle the click event for the Space button */ if (!this.ready) { this.formFeedCount = 0; @@ -255,7 +282,9 @@ B5500LinePrinter.prototype.LPFormFeedBtn_onclick = function LPFormFeedBtn_onclic this.printLine("", -1); this.endOfPaper.scrollIntoView(); if (++this.formFeedCount >= 3) { - this.ripPaper(); + if (this.window.confirm("Do you want to clear the \"paper\" from the printer?")) { + this.ripPaper(); + } } } }; @@ -266,7 +295,7 @@ B5500LinePrinter.prototype.LPEndOfPaperBtn_onclick = function LPEndOfPaperBtn_on and end-of-paper condition, this will make the printer ready, but it will still be in an EOP condition. The next time a print line is received, the EOP condition will force it not-ready again. You can print only one line - at a time (presumably to the end of the current page. The EOP condition can + at a time (presumably to the end of the current page). The EOP condition can be cleared by clicking Skip To Heading three times to "rip" the paper */ if (this.paperLeft <= 0 && !this.ready) { @@ -327,6 +356,8 @@ B5500LinePrinter.prototype.printerOnload = function printerOnload() { this.window.addEventListener("beforeunload", B5500LinePrinter.prototype.beforeUnload, false); + this.paper.addEventListener("dblclick", + B5500CentralControl.bindMethod(this, B5500LinePrinter.prototype.copyPaper)); this.$$("LPEndOfPaperBtn").addEventListener("click", B5500CentralControl.bindMethod(this, B5500LinePrinter.prototype.LPEndOfPaperBtn_onclick), false); this.$$("LPFormFeedBtn").addEventListener("click", diff --git a/webUI/B5500MagTapeDrive.js b/webUI/B5500MagTapeDrive.js index d09cdf4..4eb3a0a 100644 --- a/webUI/B5500MagTapeDrive.js +++ b/webUI/B5500MagTapeDrive.js @@ -66,7 +66,9 @@ B5500MagTapeDrive.prototype.tapeRemote = 2; B5500MagTapeDrive.prototype.density = 800; // 800 bits/inch -B5500MagTapeDrive.prototype.charsPerSec = 72000; +B5500MagTapeDrive.prototype.tapeSpeed = 90; + // tape motion speed [inches/sec] +B5500MagTapeDrive.prototype.charsPerSec = B5500MagTapeDrive.prototype.tapeSpeed*B5500MagTapeDrive.prototype.density; // B425, 90 inches/sec @ 800 bits/inch B5500MagTapeDrive.prototype.gapLength = 0.75; // inter-block blank tape gap [inches] @@ -74,8 +76,6 @@ B5500MagTapeDrive.prototype.startStopTime = 0.0045 + 0.0042; // tape start+stop time [sec] B5500MagTapeDrive.prototype.rewindSpeed = 320; // rewind speed [inches/sec] -B5500MagTapeDrive.prototype.tapeSpeed = B5500MagTapeDrive.prototype.charsPerSec/B5500MagTapeDrive.prototype.density; - // tape motion speed [inches/sec] B5500MagTapeDrive.prototype.maxTapeLength = 2410*12; // max tape length on reel [inches] B5500MagTapeDrive.prototype.postEOTLength = 20*12; @@ -159,6 +159,7 @@ B5500MagTapeDrive.prototype.clear = function clear() { this.reelAngle = 0; // current rotation angle of reel image [degrees] this.tapeInches = 0; // number of inches currently up-tape this.writeRing = false; // true if write ring is present and tape is writable + this.botSensed = false; // true if BOT marker sensed during reverse tape motion this.atBOT = true; // true if tape at BOT this.atEOT = false; // true if tape at EOT @@ -245,15 +246,16 @@ B5500MagTapeDrive.prototype.setAtBOT = function setAtBOT(atBOT) { if (atBOT ^ this.atBOT) { this.atBOT = atBOT; - if (atBOT) { + if (!atBOT) { + this.botSensed = false; + B5500Util.removeClass(this.$$("MTAtBOTLight"), "annunciatorLit"); + } else { this.imgIndex = 0; this.tapeInches = 0; B5500Util.addClass(this.$$("MTAtBOTLight"), "annunciator"); this.reelBar.value = this.imgMaxInches; - this.reelIcon.style.transform = "rotate(0deg)"; - this.reelIcon.style["-webkit-transform"] = "rotate(0deg)"; // temp for Chrome - } else { - B5500Util.removeClass(this.$$("MTAtBOTLight"), "annunciatorLit"); + this.reelIcon.style.transform = "none"; + this.reelIcon.style["-webkit-transform"] = "none"; // temp for Chrome } } }; @@ -264,11 +266,11 @@ B5500MagTapeDrive.prototype.setAtEOT = function setAtEOT(atEOT) { if (atEOT ^ this.atEOT) { this.atEOT = atEOT; - if (atEOT) { + if (!atEOT) { + B5500Util.removeClass(this.$$("MTAtEOTLight"), "annunciatorLit"); + } else { B5500Util.addClass(this.$$("MTAtEOTLight"), "annunciatorLit"); this.reelBar.value = 0; - } else { - B5500Util.removeClass(this.$$("MTAtEOTLight"), "annunciatorLit"); } } }; @@ -413,6 +415,7 @@ B5500MagTapeDrive.prototype.loadTape = function loadTape() { mt.reelBar.value = mt.imgMaxInches; mt.setAtEOT(false); mt.setAtBOT(true); + mt.botSensed = false; mt.tapeState = mt.tapeLocal; // setTapeRemote() requires it not be unloaded mt.setTapeRemote(false); mt.reelIcon.style.visibility = "visible"; @@ -471,15 +474,18 @@ B5500MagTapeDrive.prototype.loadTape = function loadTape() { function blankLoader() { /* Loads a blank tape image into the drive */ + var x; writeRing = true; eotInches = tapeInches; tapeInches += mt.postEOTLength; mt.image = new Uint8Array(new ArrayBuffer(tapeInches*mt.density)); mt.image[0] = 0x81; // put a little noise on the tape to avoid blank-tape timeouts - mt.image[1] = 0x03; - mt.image[2] = 0x8F; - mt.imgTopIndex = 3; + for (x=1; x<80; ++x) { + mt.image[x] = 0x40; + } + mt.image[80] = 0x8F; + mt.imgTopIndex = 81; finishLoad(); } @@ -741,16 +747,16 @@ B5500MagTapeDrive.prototype.tapeRewind = function tapeRewind(makeReady) { if (interval <= 0) { interval = this.spinUpdateInterval/2; } - if (this.tapeInches > 0) { + if (this.tapeInches <= 0) { + this.setAtBOT(true); + this.botSensed = true; + this.timer = setCallback(this.mnemonic, this, 2000, rewindFinish); + } else { inches = interval/1000*this.rewindSpeed; this.tapeInches -= inches; lastStamp = stamp; this.timer = setCallback(this.mnemonic, this, this.spinUpdateInterval, rewindDelay); this.spinReel(-inches); - } else { - this.setAtBOT(true); - this.timer = setCallback(this.mnemonic, this, 2000, rewindFinish); - this.spinReel(6); } } @@ -822,10 +828,8 @@ B5500MagTapeDrive.prototype.buildErrorMask = function buildErrorMask(chars) { var mask = this.errorMask & 0x01FC7FFF; // clear out the char count bits mask |= (chars & 0x07) << 15; - if (this.atBOT) { + if (this.botSensed) { mask |= 0x80000; // tape at BOT - } else if (this.atEOT) { - mask |= 0x40020; // tape at EOT } this.errorMask = mask; return mask; @@ -894,7 +898,11 @@ B5500MagTapeDrive.prototype.bcdSpaceBackward = function bcdSpaceBackward(checkEO if (imgIndex <= 0) { this.setAtBOT(true); - this.errorMask |= 0x100000; // set blank-tape bit + if (this.botSensed) { + this.errorMask |= 0x100000; // set blank-tape bit + } else { + this.botSensed = true; + } } else { if (this.atEOT) { this.setAtEOT(false); @@ -1028,7 +1036,11 @@ B5500MagTapeDrive.prototype.bcdReadBackward = function bcdReadBackward(oddParity if (imgIndex <= 0) { this.setAtBOT(true); - this.errorMask |= 0x100000; // set blank-tape bit + if (this.botSensed) { + this.errorMask |= 0x100000; // set blank-tape bit + } else { + this.botSensed = true; + } } else { if (this.atEOT) { this.setAtEOT(false); @@ -1196,7 +1208,7 @@ B5500MagTapeDrive.prototype.read = function read(finish, buffer, length, mode, c count = this.bcdReadBackward(mode); residue = 7 - count % 8; imgCount -= this.imgIndex; - inches = -imgCount/this.density - this.gapLength; + inches = (imgCount > 0 ? -imgCount/this.density - this.gapLength : 0); this.tapeInches += inches; if (this.atEOT && this.tapeInches < this.imgEOTInches) { this.setAtEOT(false); @@ -1205,10 +1217,11 @@ B5500MagTapeDrive.prototype.read = function read(finish, buffer, length, mode, c count = this.bcdReadForward(mode); residue = count % 8; imgCount = this.imgIndex - imgCount; - inches = imgCount/this.density + this.gapLength; + inches = (imgCount > 0 ? imgCount/this.density + this.gapLength : 0); this.tapeInches += inches; if (!this.atEOT && this.tapeInches > this.imgEOTInches) { this.setAtEOT(true); + this.errorMask |= 0x40020; // tape at EOT } } @@ -1249,7 +1262,7 @@ B5500MagTapeDrive.prototype.space = function space(finish, length, control) { if (control) { this.bcdSpaceBackward(true); imgCount -= this.imgIndex; - inches = -imgCount/this.density - this.gapLength; + inches = (imgCount > 0 ? -imgCount/this.density - this.gapLength : 0); this.tapeInches += inches; if (this.atEOT && this.tapeInches < this.imgEOTInches) { this.setAtEOT(false); @@ -1257,10 +1270,11 @@ B5500MagTapeDrive.prototype.space = function space(finish, length, control) { } else { this.bcdSpaceForward(true); imgCount = this.imgIndex - imgCount; - inches = imgCount/this.density + this.gapLength; + inches = (imgCount > 0 ? imgCount/this.density + this.gapLength : 0); this.tapeInches += inches; if (!this.atEOT && this.tapeInches > this.imgEOTInches) { this.setAtEOT(true); + this.errorMask |= 0x40020; // tape at EOT } } @@ -1307,6 +1321,7 @@ B5500MagTapeDrive.prototype.write = function write(finish, buffer, length, mode, this.tapeInches += inches; if (!this.atEOT && this.tapeInches > this.imgEOTInches) { this.setAtEOT(true); + this.errorMask |= 0x40020; // tape at EOT } this.imgWritten = true; @@ -1349,6 +1364,7 @@ B5500MagTapeDrive.prototype.erase = function erase(finish, length) { this.tapeInches += inches; if (!this.atEOT && this.tapeInches > this.imgEOTInches) { this.setAtEOT(true); + this.errorMask |= 0x40020; // tape at EOT } this.imgWritten = true; @@ -1410,11 +1426,19 @@ B5500MagTapeDrive.prototype.writeInterrogate = function writeInterrogate(finish, } else if (!this.ready) { finish(0x04, 0); // report unit not ready } else { - if (this.writeRing) { - this.buildErrorMask(0); - } else { - this.errorMask |= 0x50; // RD bits 26 & 28 => no write ring, don't return Mod III bits + if (!this.writeRing) { + this.errorMask |= 0x50; // RD bits 26 & 28 => no write ring } + this.buildErrorMask(0); + + /* For some reason the MCP does not like the BOT status being reported in + the result descriptor for a write interrogate. The I/O Control Unit flows + clearly show the Mod-III I/O Unit bits being set, but the MCP error mask + causes them to be seen as fatal errors. For now, we'll unconditionally + eliminate the BOT result bit in the error mask, although that just doesn't + seem right */ + this.errorMask &= 0xFF7FFFF; + finish(this.errorMask, 0); } //console.log(this.mnemonic + " writeInterrogate: c=" + control + ", mask=" + this.errorMask.toString(8)); diff --git a/webUI/B5500Manifest.appcache b/webUI/B5500Manifest.appcache index a8008c2..368512e 100644 --- a/webUI/B5500Manifest.appcache +++ b/webUI/B5500Manifest.appcache @@ -1,5 +1,5 @@ CACHE MANIFEST -# retro-B5500 emulator 1.01, 2015-02-08 17:30 +# retro-B5500 emulator 1.02, 2015-06-14 16:30 CACHE: ../emulator/B5500CentralControl.js diff --git a/webUI/B5500SPOUnit.js b/webUI/B5500SPOUnit.js index 4fb2648..12ae6c7 100644 --- a/webUI/B5500SPOUnit.js +++ b/webUI/B5500SPOUnit.js @@ -88,6 +88,7 @@ B5500SPOUnit.prototype.clear = function clear() { this.nextCharTime = 0; this.spoState = this.spoLocal; // Current state of SPO interface + this.spoInputRequested = false; // INPUT REQUEST button pressed this.spoLocalRequested = false; // LOCAL button pressed while active }; @@ -96,6 +97,7 @@ B5500SPOUnit.prototype.setLocal = function setLocal() { /* Sets the status of the SPO to Local and enables the input element */ this.spoLocalRequested = false; + this.spoInputRequested = false; this.spoState = this.spoLocal; this.endOfPaper.scrollIntoView(); B5500Util.addClass(this.$$("SPOLocalBtn"), "yellowLit"); @@ -134,6 +136,7 @@ B5500SPOUnit.prototype.setRemote = function setRemote() { if (this.spoState == this.spoLocal) { this.spoState = this.spoRemote; this.spoLocalRequested = false; + this.spoInputRequested = false; B5500Util.addClass(this.$$("SPORemoteBtn"), "yellowLit"); B5500Util.removeClass(this.$$("SPOLocalBtn"), "yellowLit"); B5500Util.removeClass(this.inputBox, "visible"); @@ -212,7 +215,8 @@ B5500SPOUnit.prototype.printChar = function printChar(c) { line += s; ++this.printCol; } else { - line = line.substring(0, 71) + s; + line = s; + this.appendEmptyLine(); } this.paper.lastChild.nodeValue = line; }; @@ -259,9 +263,19 @@ B5500SPOUnit.prototype.requestInput = function requestInput() { /* Handles the request for keyboard input, from either the Input Request button or the ESC key */ - if (this.spoState == this.spoRemote || this.spoState == this.spoOutput) { - B5500Util.addClass(this.$$("SPOInputRequestBtn"), "yellowLit"); - this.signal(); + switch (this.spoState) { + case this.spoRemote: + case this.spoOutput: + if (!this.spoInputRequested) { + this.spoInputRequested = true; + B5500Util.addClass(this.$$("SPOInputRequestBtn"), "yellowLit"); + this.signal(); + } + break; + case this.spoInput: + // the second click moved focus out of the SPO input control + this.inputBox.focus(); + break; } }; @@ -400,7 +414,7 @@ B5500SPOUnit.prototype.keyDown = function keyDown(ev) { B5500SPOUnit.prototype.copyPaper = function copyPaper(ev) { /* Copies the text contents of the "paper" area of the SPO, opens a new temporary window, and pastes that text into the window so it can be copied - or saved */ + or saved by the user */ var text = ev.target.textContent; var title = "B5500 " + this.mnemonic + " Text Snapshot"; var win = window.open("./B5500FramePaper.html", this.mnemonic + "-Snapshot", @@ -501,8 +515,8 @@ B5500SPOUnit.prototype.spoOnload = function spoOnload() { this.printText("retro-B5500 Emulator Version " + B5500CentralControl.version, B5500CentralControl.bindMethod(this, function initFinish() { - //window.open("", "B5500Console").focus(); this.window.focus(); + window.open("", "B5500Console").focus(); this.setRemote(); this.appendEmptyLine("\xA0"); this.endOfPaper.scrollIntoView(); @@ -521,6 +535,7 @@ B5500SPOUnit.prototype.read = function read(finish, buffer, length, mode, contro switch (this.spoState) { case this.spoRemote: this.spoState = this.spoInput; + this.spoInputRequested = false; B5500Util.addClass(this.$$("SPOReadyBtn"), "yellowLit"); B5500Util.removeClass(this.$$("SPOInputRequestBtn"), "yellowLit"); this.endOfPaper.scrollIntoView(); diff --git a/webUI/B5500SetCallback.js b/webUI/B5500SetCallback.js index 98f6b37..e0df299 100644 --- a/webUI/B5500SetCallback.js +++ b/webUI/B5500SetCallback.js @@ -84,8 +84,8 @@ (function (global) { /* Define a closure for the setCallback() mechanism */ - var delayAlpha = 0.99; // exponential-moving-average decay factor - var delayDev = {NUL: 0}; // hash of average delay time deviations by category + var delayAlpha = 0.25; // delay deviation decay factor + var delayDev = {NUL: 0}; // hash of delay time deviations by category var minTimeout = 4; // minimum setTimeout() threshold, milliseconds var nextTokenNr = 1; // next setCallback token return value var pendingCallbacks = {}; // hash of pending callbacks, indexed by token as a string @@ -107,8 +107,7 @@ delete pendingCallbacks[tokenName]; category = thisCallback.category; if (category) { - delayDev[category] = (delayDev[category] || 0)*delayAlpha + - (endStamp - thisCallback.startStamp - thisCallback.delay)*(1.0-delayAlpha); + delayDev[category] += endStamp - thisCallback.startStamp - thisCallback.delay; } try { thisCallback.fcn.call(thisCallback.context, thisCallback.arg); @@ -152,6 +151,7 @@ setTimeout mechanism will be used */ var categoryName = (category || "NUL").toString(); var delay = callbackDelay || 0; + var delayBias; var thisCallback; var token = nextTokenNr++; var tokenName = token.toString(); @@ -175,7 +175,14 @@ pendingCallbacks[tokenName] = thisCallback; // Decide whether to do a time wait or just a yield. - delay -= (delayDev[categoryName] || 0); // bias by the current avg. deviation + if (categoryName in delayDev) { + delayBias = delayDev[categoryName]*delayAlpha; + delayDev[categoryName] -= delayBias; + delay -= delayBias; + } else { + delayDev[categoryName] = 0; // got a new one + } + if (delay < minTimeout) { thisCallback.isTimeout = false; thisCallback.cancelToken = 0;