cpu features added. text-mode simulator interface under dev

This commit is contained in:
wixette 2020-03-06 22:15:26 +08:00
parent 43ba3c8252
commit c76a8142b7
2 changed files with 290 additions and 72 deletions

View File

@ -1,23 +1,109 @@
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<meta charset="utf-8">
<style>
body {
margin: 30px;
font-family: monospace;
}
</style>
</head> </head>
<body onload="test();"> <body>
<h2>Text-mode Altair 8800 simulator</h2>
<h1>test</h1> <hr>
<p>
<input type="button" value="INIT" onclick="init();">
<input type="button" value="TEST" onclick="test();">
<input type="button" value="RUN" onclick="run();">
<input type="button" value="STOP" onclick="stop();">
</p>
<hr>
<p>LEDs: <!-- &#x25cf; - on. &#x25cb; - off. -->
<div id="data-leds">
<p>
D7&nbsp;D6&nbsp;D5&nbsp;D4&nbsp;D3&nbsp;D2&nbsp;D1&nbsp;D0<br>
<span id="d7" class="led">&#x25cb;</span>&nbsp;
<span id="d6" class="led">&#x25cb;</span>&nbsp;
<span id="d5" class="led">&#x25cb;</span>&nbsp;
<span id="d4" class="led">&#x25cb;</span>&nbsp;
<span id="d3" class="led">&#x25cb;</span>&nbsp;
<span id="d2" class="led">&#x25cb;</span>&nbsp;
<span id="d1" class="led">&#x25cb;</span>&nbsp;
<span id="d0" class="led">&#x25cb;</span>&nbsp;
</p>
</div>
<div id="address-leds">
<p>
A15&nbsp;A14&nbsp;A13&nbsp;A12&nbsp;A11&nbsp;A10&nbsp;A9&nbsp;&nbsp;A8&nbsp;&nbsp;A7&nbsp;&nbsp;A6&nbsp;&nbsp;A5&nbsp;&nbsp;A4&nbsp;&nbsp;A3&nbsp;&nbsp;A2&nbsp;&nbsp;A1&nbsp;&nbsp;A0<br>
<span id="a15" class="led">&#x25cb;</span>&nbsp;&nbsp;
<span id="a14" class="led">&#x25cb;</span>&nbsp;&nbsp;
<span id="a13" class="led">&#x25cb;</span>&nbsp;&nbsp;
<span id="a12" class="led">&#x25cb;</span>&nbsp;&nbsp;
<span id="a11" class="led">&#x25cb;</span>&nbsp;&nbsp;
<span id="a10" class="led">&#x25cb;</span>&nbsp;&nbsp;
<span id="a9" class="led">&#x25cb;</span>&nbsp;&nbsp;
<span id="a8" class="led">&#x25cb;</span>&nbsp;&nbsp;
<span id="a7" class="led">&#x25cb;</span>&nbsp;&nbsp;
<span id="a6" class="led">&#x25cb;</span>&nbsp;&nbsp;
<span id="a5" class="led">&#x25cb;</span>&nbsp;&nbsp;
<span id="a4" class="led">&#x25cb;</span>&nbsp;&nbsp;
<span id="a3" class="led">&#x25cb;</span>&nbsp;&nbsp;
<span id="a2" class="led">&#x25cb;</span>&nbsp;&nbsp;
<span id="a1" class="led">&#x25cb;</span>&nbsp;&nbsp;
<span id="a0" class="led">&#x25cb;</span>&nbsp;&nbsp;
</p>
</div>
</p>
<hr>
<p>CPU dump:
<div id="cpu"></div> <div id="cpu"></div>
</p>
<hr>
<p>Memory dump:
<div id="mem"></div> <div id="mem"></div>
</p>
<script src="js/8080.js"></script> <script src="js/8080.js"></script>
<script src="js/sim8800.js"></script> <script src="js/sim8800.js"></script>
<script> <script>
function test() {
var sim = new Sim8800(256, 100000);
var dumpCpuElem = document.getElementById('cpu'); var dumpCpuElem = document.getElementById('cpu');
var dumpMemElem = document.getElementById('mem'); var dumpMemElem = document.getElementById('mem');
sim.dumpCpu(dumpCpuElem); var sim = new Sim8800(256, 1000000,
sim.dumpMem(dumpMemElem); dumpCpuElem, dumpMemElem);
function init() {
sim.loadDataAsHexString(0, 'db ff d3 ff c3 00 00');
sim.loadData(16, [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]);
sim.dumpCpu();
sim.dumpMem()
}
function test() {
console.log(Sim8800.parseBits(10, 1));
console.log(Sim8800.parseBits(0xF8, 1));
console.log(Sim8800.parseBits(0xF0, 2));
}
function run() {
sim.loadDataAsHexString(0, 'db ff d3 ff c3 00 00');
sim.start();
sim.dumpCpu();
sim.dumpMem()
}
function stop() {
sim.stop();
sim.dumpCpu();
sim.dumpMem()
} }
</script> </script>
</body> </body>

View File

@ -24,12 +24,18 @@ class Sim8800 {
/** /**
* @param {number} memSize The memory size, in bytes. * @param {number} memSize The memory size, in bytes.
* @param {number} clockRate The clock rate. * @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) { constructor(memSize, clockRate,
/** @type {number} */ dumpCpuElem, dumpMemElem) {
this.clockRate = clockRate; this.clockRate = clockRate;
/** @type {Array<number>} */
this.mem = new Array(memSize); this.mem = new Array(memSize);
this.dumpCpuElem = dumpCpuElem;
this.dumpMemElem = dumpMemElem;
this.running = false;
this.initMem(); this.initMem();
CPU8080.init(this.getWriteByteCallback(), CPU8080.init(this.getWriteByteCallback(),
this.getReadByteCallback(), this.getReadByteCallback(),
@ -38,6 +44,135 @@ class Sim8800 {
this.getReadPortCallback()); 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<number>} 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<number>} 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 = ['<pre>\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('</pre>\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 = ['<pre>\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('</pre>\n');
this.dumpCpuElem.innerHTML = sb.join('');
}
}
/** /**
* Returns the byteTo (write memory) callback. * Returns the byteTo (write memory) callback.
* @return {function(number, number)} * @return {function(number, number)}
@ -47,8 +182,6 @@ class Sim8800 {
return function(address, value) { return function(address, value) {
address = address % self.mem.length; address = address % self.mem.length;
self.mem[address] = value; 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) { return function(address) {
address = address % self.mem.length; address = address % self.mem.length;
var value = self.mem[address]; var value = self.mem[address];
window.console.log('reading byte @' + Sim8800.toHex(address, 8)
+ ' : ' + Sim8800.toHex(value, 2));
return value; return value;
}; };
} }
@ -74,8 +205,6 @@ class Sim8800 {
getWritePortCallback() { getWritePortCallback() {
var self = this; var self = this;
return function(address, value) { return function(address, value) {
window.console.log('writing port @' + Sim8800.toHex(address, 8)
+ ' : ' + Sim8800.toHex(value, 2));
if (address == 0xff) { if (address == 0xff) {
// We only care about port 0xff. // We only care about port 0xff.
} }
@ -93,70 +222,73 @@ class Sim8800 {
if (address == 0xff) { if (address == 0xff) {
// We only care about port 0xff. // We only care about port 0xff.
} }
window.console.log('reading port @' + Sim8800.toHex(address, 8)
+ ' : ' + Sim8800.toHex(value, 2));
return value; return value;
}; };
} }
/** /**
* Formats a number to fixed length hex string. * Gets the clock ticker callback.
* @param {number} n The number to be formatted. * @return {function()}
* @param {number} len The output length, with leading zeros.
*/ */
static toHex(n, len) { getClockTickerCallback() {
var leadingZeros = (new Array(len)).fill('0').join(''); var self = this;
return (leadingZeros + n.toString(16)).substr(-len); return function(timestamp) {
} if (self.running) {
var cycles = self.clockRate / 1000;
/** CPU8080.steps(cycles);
* Fills the memory with random numbers. self.dumpCpu();
*/ self.dumpMem();
initMem() { window.setTimeout(self.getClockTickerCallback(), 1);
for (let i = 0; i < this.mem.length; i++) { }
this.mem[i] = Math.floor(Math.random() * 256); };
} }
}
/**
/** * Runs a single CPU step.
* Dumps the memory to HTML, for debugging or monitoring. */
* @param {Element} containerElem The DOM element to hold the generated HTML. singleStep() {
*/ CPU8080.steps(1);
dumpMem(containerElem) { this.dumpCpu();
var sb = ['<pre>\n']; this.dumpMem();
for (let i = 0; i < this.mem.length; i += 16) { }
sb.push(Sim8800.toHex(i, 4));
sb.push(' '); /**
for (let j = i; * Powers on the machine.
j < Math.min(this.mem.length, i + 16); */
j++) { powerOn() {
sb.push(Sim8800.toHex(this.mem[j], 2)); this.stop();
sb.push((j + 1) % 8 == 0 ? ' ' : ' '); reset();
} this.initMem();
sb.push('\n'); }
}
sb.push('</pre>\n'); /**
containerElem.innerHTML = sb.join(''); * Powers off the machine.
} */
powerOff() {
/** this.stop();
* Dumps the internal CPU status to HTML, for debugging or mornitoring. reset();
* @param {Element} containerElem The DOM element to hold the generated HTML. this.initMem();
*/ }
dumpCpu(containerElem) {
var cpu = CPU8080.status(); /**
var sb = ['<pre>\n']; * Resets the machine.
sb.push('PC = ' + Sim8800.toHex(cpu.pc, 4) + ' '); */
sb.push('SP = ' + Sim8800.toHex(cpu.sp, 4) + '\n'); reset() {
sb.push('A = ' + Sim8800.toHex(cpu.a, 2) + ' '); CPU8080.reset();
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) + ' '); * Stops the CPU.
sb.push('F = ' + Sim8800.toHex(cpu.f, 2) + ' '); */
sb.push('H = ' + Sim8800.toHex(cpu.h, 2) + ' '); stop() {
sb.push('L = ' + Sim8800.toHex(cpu.l, 2) + '\n'); this.running = false;
sb.push('</pre>\n'); }
containerElem.innerHTML = sb.join('');
/**
* Starts the CPU.
*/
start() {
this.running = true;
window.setTimeout(this.getClockTickerCallback(), 1);
} }
}; };