cpu features added. text-mode simulator interface under dev
This commit is contained in:
parent
43ba3c8252
commit
c76a8142b7
98
index.html
98
index.html
|
@ -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: <!-- ● - on. ○ - off. -->
|
||||||
|
<div id="data-leds">
|
||||||
|
<p>
|
||||||
|
D7 D6 D5 D4 D3 D2 D1 D0<br>
|
||||||
|
<span id="d7" class="led">○</span>
|
||||||
|
<span id="d6" class="led">○</span>
|
||||||
|
<span id="d5" class="led">○</span>
|
||||||
|
<span id="d4" class="led">○</span>
|
||||||
|
<span id="d3" class="led">○</span>
|
||||||
|
<span id="d2" class="led">○</span>
|
||||||
|
<span id="d1" class="led">○</span>
|
||||||
|
<span id="d0" class="led">○</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div id="address-leds">
|
||||||
|
<p>
|
||||||
|
A15 A14 A13 A12 A11 A10 A9 A8 A7 A6 A5 A4 A3 A2 A1 A0<br>
|
||||||
|
<span id="a15" class="led">○</span>
|
||||||
|
<span id="a14" class="led">○</span>
|
||||||
|
<span id="a13" class="led">○</span>
|
||||||
|
<span id="a12" class="led">○</span>
|
||||||
|
<span id="a11" class="led">○</span>
|
||||||
|
<span id="a10" class="led">○</span>
|
||||||
|
<span id="a9" class="led">○</span>
|
||||||
|
<span id="a8" class="led">○</span>
|
||||||
|
<span id="a7" class="led">○</span>
|
||||||
|
<span id="a6" class="led">○</span>
|
||||||
|
<span id="a5" class="led">○</span>
|
||||||
|
<span id="a4" class="led">○</span>
|
||||||
|
<span id="a3" class="led">○</span>
|
||||||
|
<span id="a2" class="led">○</span>
|
||||||
|
<span id="a1" class="led">○</span>
|
||||||
|
<span id="a0" class="led">○</span>
|
||||||
|
</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>
|
||||||
|
|
244
js/sim8800.js
244
js/sim8800.js
|
@ -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);
|
||||||
|
self.dumpCpu();
|
||||||
|
self.dumpMem();
|
||||||
|
window.setTimeout(self.getClockTickerCallback(), 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fills the memory with random numbers.
|
* Runs a single CPU step.
|
||||||
*/
|
*/
|
||||||
initMem() {
|
singleStep() {
|
||||||
for (let i = 0; i < this.mem.length; i++) {
|
CPU8080.steps(1);
|
||||||
this.mem[i] = Math.floor(Math.random() * 256);
|
this.dumpCpu();
|
||||||
}
|
this.dumpMem();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dumps the memory to HTML, for debugging or monitoring.
|
* Powers on the machine.
|
||||||
* @param {Element} containerElem The DOM element to hold the generated HTML.
|
|
||||||
*/
|
*/
|
||||||
dumpMem(containerElem) {
|
powerOn() {
|
||||||
var sb = ['<pre>\n'];
|
this.stop();
|
||||||
for (let i = 0; i < this.mem.length; i += 16) {
|
reset();
|
||||||
sb.push(Sim8800.toHex(i, 4));
|
this.initMem();
|
||||||
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');
|
|
||||||
containerElem.innerHTML = sb.join('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dumps the internal CPU status to HTML, for debugging or mornitoring.
|
* Powers off the machine.
|
||||||
* @param {Element} containerElem The DOM element to hold the generated HTML.
|
|
||||||
*/
|
*/
|
||||||
dumpCpu(containerElem) {
|
powerOff() {
|
||||||
var cpu = CPU8080.status();
|
this.stop();
|
||||||
var sb = ['<pre>\n'];
|
reset();
|
||||||
sb.push('PC = ' + Sim8800.toHex(cpu.pc, 4) + ' ');
|
this.initMem();
|
||||||
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) + ' ');
|
* Resets the machine.
|
||||||
sb.push('D = ' + Sim8800.toHex(cpu.d, 2) + '\n');
|
*/
|
||||||
sb.push('E = ' + Sim8800.toHex(cpu.e, 2) + ' ');
|
reset() {
|
||||||
sb.push('F = ' + Sim8800.toHex(cpu.f, 2) + ' ');
|
CPU8080.reset();
|
||||||
sb.push('H = ' + Sim8800.toHex(cpu.h, 2) + ' ');
|
}
|
||||||
sb.push('L = ' + Sim8800.toHex(cpu.l, 2) + '\n');
|
|
||||||
sb.push('</pre>\n');
|
/**
|
||||||
containerElem.innerHTML = sb.join('');
|
* Stops the CPU.
|
||||||
|
*/
|
||||||
|
stop() {
|
||||||
|
this.running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the CPU.
|
||||||
|
*/
|
||||||
|
start() {
|
||||||
|
this.running = true;
|
||||||
|
window.setTimeout(this.getClockTickerCallback(), 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue
Block a user