diff --git a/index.html b/index.html index d448a29..1eccac7 100644 --- a/index.html +++ b/index.html @@ -1,23 +1,109 @@ + + - + +

Text-mode Altair 8800 simulator

-

test

+
-
-
+

+ + + + +

+ +
+ +

LEDs: +

+

+ D7 D6 D5 D4 D3 D2 D1 D0
+   +   +   +   +   +   +   +   +

+
+
+

+ A15 A14 A13 A12 A11 A10 A9  A8  A7  A6  A5  A4  A3  A2  A1  A0
+    +    +    +    +    +    +    +    +    +    +    +    +    +    +    +    +

+
+

+ +
+ +

CPU dump: +

+

+ +
+ +

Memory dump: +

+

diff --git a/js/sim8800.js b/js/sim8800.js index 4d94978..4f43d31 100644 --- a/js/sim8800.js +++ b/js/sim8800.js @@ -24,12 +24,18 @@ class Sim8800 { /** * @param {number} memSize The memory size, in bytes. * @param {number} clockRate The clock rate. + * @param {Element?} dumpCpuElem The DOM element used to render + * dumped CPU status. null to disable the feature. + * @param {Element?} dumpMemElem The DOM element used to render + * dumped memory contents. null to disable the feature. */ - constructor(memSize, clockRate) { - /** @type {number} */ + constructor(memSize, clockRate, + dumpCpuElem, dumpMemElem) { this.clockRate = clockRate; - /** @type {Array} */ this.mem = new Array(memSize); + this.dumpCpuElem = dumpCpuElem; + this.dumpMemElem = dumpMemElem; + this.running = false; this.initMem(); CPU8080.init(this.getWriteByteCallback(), this.getReadByteCallback(), @@ -38,6 +44,135 @@ class Sim8800 { this.getReadPortCallback()); } + /** + * Formats a number to fixed length hex string. + * @param {number} n The number to be formatted. + * @param {number} len The output length, with leading zeros. + */ + static toHex(n, len) { + var leadingZeros = (new Array(len)).fill('0').join(''); + return (leadingZeros + n.toString(16)).substr(-len); + } + + /** + * Parses a number into an array of binary bits. + * @param {number} data The data to be parsed. + * @param {number} numBytes Number of bytes to be parsed. + * @return {Array} Sequence of 0 or 1, from the lowest bit to + * the highest bit. + */ + static parseBits(data, numBytes) { + var bits = []; + for (let i = 0; i < numBytes * 8; i++) { + bits.push(data & 1 != 0 ? 1 : 0); + data >>>= 1; + } + return bits; + } + + /** + * Fills the memory with dummy bytes. + */ + initMem(random = false) { + for (let i = 0; i < this.mem.length; i++) { + if (random) { + this.mem[i] = Math.floor(Math.random() * 256); + } else { + this.mem[i] = 0; + } + } + } + + /** + * Loads data into memory. + * @param {number} address The start address to load the data/program. + * @param {Array} data The array of data. + */ + loadData(address, data) { + for (let i = 0; i < data.length && address < this.mem.length; i++) { + this.mem[address++] = data[i]; + } + } + + /** + * Loads data into memory. + * @param {number} address The start address to load the data/program. + * @param {string} hexString Data encoded in hex string, like 'c3 00 00'. + */ + loadDataAsHexString(address, hexString) { + var data = hexString.split(' '); + for (let i = 0; i < data.length && address < this.mem.length; i++) { + var byte = parseInt('0x' + data[i]); + this.mem[address++] = byte; + } + } + + /** + * Dumps the memory to HTML, for debugging or monitoring. + */ + dumpMem() { + if (this.dumpMemElem) { + var sb = ['
\n'];
+            for (let i = 0; i < this.mem.length; i += 16) {
+                sb.push(Sim8800.toHex(i, 4));
+                sb.push('  ');
+                for (let j = i;
+                     j < Math.min(this.mem.length, i + 16);
+                     j++) {
+                    sb.push(Sim8800.toHex(this.mem[j], 2));
+                    sb.push((j + 1) % 8 == 0 ? '  ' : ' ');
+                }
+                sb.push('\n');
+            }
+            sb.push('
\n'); + this.dumpMemElem.innerHTML = sb.join(''); + } + } + + /** + * Decodes the FLAGs register. + * @param {number} flags The value of the FLAGs register. + * @return {Object} The decoded flags. + */ + decodeFlags(flags) { + var ret = {}; + ret.sign = flags & 0x80 != 0; + ret.zero = flags & 0x40 != 0; + ret.auxiliaryCarry = flags & 0x10 != 0; + ret.parity = flags & 0x04 != 0; + ret.carry = flags & 0x01 != 0; + return ret; + } + + /** + * Dumps the internal CPU status to HTML, for debugging or mornitoring. + */ + dumpCpu() { + if (this.dumpCpuElem) { + var cpu = CPU8080.status(); + var sb = ['
\n'];
+            sb.push('PC = ' + Sim8800.toHex(cpu.pc, 4) + '  ');
+            sb.push('SP = ' + Sim8800.toHex(cpu.sp, 4) + '\n');
+            sb.push('A = ' + Sim8800.toHex(cpu.a, 2) + '  ');
+            sb.push('B = ' + Sim8800.toHex(cpu.b, 2) + '  ');
+            sb.push('C = ' + Sim8800.toHex(cpu.c, 2) + '  ');
+            sb.push('D = ' + Sim8800.toHex(cpu.d, 2) + '\n');
+            sb.push('E = ' + Sim8800.toHex(cpu.e, 2) + '  ');
+            sb.push('F = ' + Sim8800.toHex(cpu.f, 2) + '  ');
+            sb.push('H = ' + Sim8800.toHex(cpu.h, 2) + '  ');
+            sb.push('L = ' + Sim8800.toHex(cpu.l, 2) + '\n');
+            var flags = this.decodeFlags(cpu.f);
+            sb.push('FLAGS: ');
+            if (flags.sign) sb.push('SIGN ');
+            if (flags.zero) sb.push('ZERO ');
+            if (flags.auxiliaryCarry) sb.push('AC ');
+            if (flags.parity) sb.push('PARITY ');
+            if (flags.carry) sb.push('CARRY ');
+            sb.push('
\n'); + this.dumpCpuElem.innerHTML = sb.join(''); + } + } + /** * Returns the byteTo (write memory) callback. * @return {function(number, number)} @@ -47,8 +182,6 @@ class Sim8800 { return function(address, value) { address = address % self.mem.length; self.mem[address] = value; - window.console.log('writing byte @' + Sim8800.toHex(address, 8) - + ' : ' + Sim8800.toHex(value, 2)); }; } @@ -61,8 +194,6 @@ class Sim8800 { return function(address) { address = address % self.mem.length; var value = self.mem[address]; - window.console.log('reading byte @' + Sim8800.toHex(address, 8) - + ' : ' + Sim8800.toHex(value, 2)); return value; }; } @@ -74,8 +205,6 @@ class Sim8800 { getWritePortCallback() { var self = this; return function(address, value) { - window.console.log('writing port @' + Sim8800.toHex(address, 8) - + ' : ' + Sim8800.toHex(value, 2)); if (address == 0xff) { // We only care about port 0xff. } @@ -93,70 +222,73 @@ class Sim8800 { if (address == 0xff) { // We only care about port 0xff. } - window.console.log('reading port @' + Sim8800.toHex(address, 8) - + ' : ' + Sim8800.toHex(value, 2)); return value; }; } /** - * Formats a number to fixed length hex string. - * @param {number} n The number to be formatted. - * @param {number} len The output length, with leading zeros. + * Gets the clock ticker callback. + * @return {function()} */ - static toHex(n, len) { - var leadingZeros = (new Array(len)).fill('0').join(''); - return (leadingZeros + n.toString(16)).substr(-len); - } - - /** - * Fills the memory with random numbers. - */ - initMem() { - for (let i = 0; i < this.mem.length; i++) { - this.mem[i] = Math.floor(Math.random() * 256); - } - } - - /** - * Dumps the memory to HTML, for debugging or monitoring. - * @param {Element} containerElem The DOM element to hold the generated HTML. - */ - dumpMem(containerElem) { - var sb = ['
\n'];
-        for (let i = 0; i < this.mem.length; i += 16) {
-            sb.push(Sim8800.toHex(i, 4));
-            sb.push('  ');
-            for (let j = i;
-                 j < Math.min(this.mem.length, i + 16);
-                 j++) {
-                sb.push(Sim8800.toHex(this.mem[j], 2));
-                sb.push((j + 1) % 8 == 0 ? '  ' : ' ');
+    getClockTickerCallback() {
+        var self = this;
+        return function(timestamp) {
+            if (self.running) {
+                var cycles = self.clockRate / 1000;
+                CPU8080.steps(cycles);
+                self.dumpCpu();
+                self.dumpMem();
+                window.setTimeout(self.getClockTickerCallback(), 1);
             }
-            sb.push('\n');
-        }
-        sb.push('
\n'); - containerElem.innerHTML = sb.join(''); + }; } /** - * Dumps the internal CPU status to HTML, for debugging or mornitoring. - * @param {Element} containerElem The DOM element to hold the generated HTML. + * Runs a single CPU step. */ - dumpCpu(containerElem) { - var cpu = CPU8080.status(); - var sb = ['
\n'];
-        sb.push('PC = ' + Sim8800.toHex(cpu.pc, 4) + '  ');
-        sb.push('SP = ' + Sim8800.toHex(cpu.sp, 4) + '\n');
-        sb.push('A = ' + Sim8800.toHex(cpu.a, 2) + '  ');
-        sb.push('B = ' + Sim8800.toHex(cpu.b, 2) + '  ');
-        sb.push('C = ' + Sim8800.toHex(cpu.c, 2) + '  ');
-        sb.push('D = ' + Sim8800.toHex(cpu.d, 2) + '\n');
-        sb.push('E = ' + Sim8800.toHex(cpu.e, 2) + '  ');
-        sb.push('F = ' + Sim8800.toHex(cpu.f, 2) + '  ');
-        sb.push('H = ' + Sim8800.toHex(cpu.h, 2) + '  ');
-        sb.push('L = ' + Sim8800.toHex(cpu.l, 2) + '\n');
-        sb.push('
\n'); - containerElem.innerHTML = sb.join(''); + singleStep() { + CPU8080.steps(1); + this.dumpCpu(); + this.dumpMem(); + } + + /** + * Powers on the machine. + */ + powerOn() { + this.stop(); + reset(); + this.initMem(); + } + + /** + * Powers off the machine. + */ + powerOff() { + this.stop(); + reset(); + this.initMem(); + } + + /** + * Resets the machine. + */ + reset() { + CPU8080.reset(); + } + + /** + * Stops the CPU. + */ + stop() { + this.running = false; + } + + /** + * Starts the CPU. + */ + start() { + this.running = true; + window.setTimeout(this.getClockTickerCallback(), 1); } };