284 lines
6.8 KiB
JavaScript
284 lines
6.8 KiB
JavaScript
/**
|
|
* @return {Uint8} a random Uint8
|
|
* */
|
|
function randomUint8() {
|
|
return Math.floor(Math.random() * 256);
|
|
}
|
|
|
|
/** Gigatron processor */
|
|
export class Gigatron {
|
|
/** Create a Gigatron
|
|
* @param {Object} options
|
|
*/
|
|
constructor(options) {
|
|
this.hz = options.hz || 6250000;
|
|
this.rom = new Uint16Array(1 << (options.romAddressWidth || 16));
|
|
this.romMask = this.rom.length - 1;
|
|
this.ram = new Uint8Array(1 << (options.ramAddressWidth || 15));
|
|
this.ramMask = this.ram.length - 1;
|
|
this.reset();
|
|
// randomize ram
|
|
for (let i = 0; i < this.ram.length; i++) {
|
|
this.ram[i] = randomUint8();
|
|
}
|
|
console.log(this.ram.length, this.ramMask);
|
|
|
|
}
|
|
|
|
/** reset registers to power-on state */
|
|
reset() {
|
|
this.pc = 0;
|
|
this.nextpc = (this.pc + 1) & this.romMask;
|
|
this.ac = 0;
|
|
this.x = 0;
|
|
this.y = 0;
|
|
this.out = 0;
|
|
this.outx = 0;
|
|
this.inReg = 0xff; // active low!
|
|
this.ctrl = 0x7c;
|
|
this.bank = 0;
|
|
this.prevctrl = -1;
|
|
this.miso = 0;
|
|
}
|
|
|
|
/** advance simulation by one tick */
|
|
tick() {
|
|
let pc = this.pc;
|
|
this.pc = this.nextpc;
|
|
this.nextpc = (this.pc + 1) & this.romMask;
|
|
this.prevctrl = -1;
|
|
|
|
let ir = this.rom[pc];
|
|
let op = (ir >> 13) & 0x0007;
|
|
let mode = (ir >> 10) & 0x0007;
|
|
let bus = (ir >> 8) & 0x0003;
|
|
let d = (ir >> 0) & 0x00ff;
|
|
|
|
switch (op) {
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
this.aluOp(op, mode, bus, d);
|
|
break;
|
|
case 6:
|
|
this.storeOp(mode, bus, d);
|
|
break;
|
|
case 7:
|
|
this.branchOp(mode, bus, d);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/** perform an alu op
|
|
* @param {number} op
|
|
* @param {number} mode
|
|
* @param {number} bus
|
|
* @param {number} d
|
|
*/
|
|
aluOp(op, mode, bus, d)
|
|
{
|
|
let b = 0;
|
|
switch (bus) {
|
|
case 0:
|
|
b = d;
|
|
break;
|
|
case 1:
|
|
let addr = this.addr(mode, d);
|
|
if (this.ctrl & 1) {
|
|
b = this.miso;
|
|
} else {
|
|
if (addr & 0x8000) { addr = addr ^ this.bank; }
|
|
b = this.ram[addr & this.ramMask];
|
|
}
|
|
break;
|
|
case 2:
|
|
b = this.ac;
|
|
break;
|
|
case 3:
|
|
b = this.inReg;
|
|
break;
|
|
}
|
|
|
|
switch (op) {
|
|
case 1:
|
|
b = this.ac & b;
|
|
break;
|
|
case 2:
|
|
b = this.ac | b;
|
|
break;
|
|
case 3:
|
|
b = this.ac ^ b;
|
|
break;
|
|
case 4:
|
|
b = (this.ac + b) & 0xff;
|
|
break;
|
|
case 5:
|
|
b = (this.ac - b) & 0xff;
|
|
break;
|
|
}
|
|
|
|
switch (mode) {
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
this.ac = b;
|
|
break;
|
|
case 4:
|
|
this.x = b;
|
|
break;
|
|
case 5:
|
|
this.y = b;
|
|
break;
|
|
case 6:
|
|
case 7:
|
|
let rising = ~this.out & b;
|
|
this.out = b;
|
|
// rising edge of out[6] registers outx from ac
|
|
if (rising & 0x40) {
|
|
this.outx = this.ac;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
/** perform a store op
|
|
* @param {number} mode
|
|
* @param {number} bus
|
|
* @param {number} d
|
|
*/
|
|
storeOp(mode, bus, d)
|
|
{
|
|
let b = 0;
|
|
let w = 1;
|
|
let addr = this.addr(mode, d);
|
|
switch (bus) {
|
|
case 0:
|
|
b = d;
|
|
break;
|
|
case 1:
|
|
if (this.ram.length <= 65536) {
|
|
b = 0;
|
|
console.error('UNDEFINED BEHAVIOR!');
|
|
} else {
|
|
this.prevctrl = this.ctrl;
|
|
this.ctrl = addr & 0x80fd;
|
|
this.bank = ((this.ctrl & 0xc0) << 9) ^ 0x8000;
|
|
w = 0;
|
|
}
|
|
break;
|
|
case 2:
|
|
b = this.ac;
|
|
break;
|
|
case 3:
|
|
b = this.inReg;
|
|
break;
|
|
}
|
|
if (w) {
|
|
if (addr & 0x8000) { addr = addr ^ this.bank; }
|
|
this.ram[addr & this.ramMask] = b;
|
|
}
|
|
switch (mode) {
|
|
case 4:
|
|
this.x = this.ac;
|
|
break;
|
|
case 5:
|
|
this.y = this.ac;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/** perform a branch op
|
|
* @param {number} mode
|
|
* @param {number} bus
|
|
* @param {number} d
|
|
*/
|
|
branchOp(mode, bus, d) {
|
|
const ZERO = 0x80;
|
|
let c = true;
|
|
let ac = this.ac ^ ZERO;
|
|
let base = this.pc & 0xff00;
|
|
|
|
switch (mode) {
|
|
case 0: // jmp
|
|
base = this.y << 8;
|
|
break;
|
|
case 1: // bgt
|
|
c = ac > ZERO;
|
|
break;
|
|
case 2: // blt
|
|
c = ac < ZERO;
|
|
break;
|
|
case 3: // bne
|
|
c = ac != ZERO;
|
|
break;
|
|
case 4: // beq
|
|
c = ac == ZERO;
|
|
break;
|
|
case 5: // bge
|
|
c = ac >= ZERO;
|
|
break;
|
|
case 6: // ble
|
|
c = ac <= ZERO;
|
|
break;
|
|
case 7: // bra
|
|
c = true;
|
|
break;
|
|
}
|
|
|
|
if (c) {
|
|
let b = this.offset(bus, d);
|
|
this.nextpc = base | b;
|
|
}
|
|
}
|
|
|
|
/** calculate a ram address
|
|
* @param {number} mode
|
|
* @param {number} d
|
|
* @return {number} the address
|
|
*/
|
|
addr(mode, d) {
|
|
switch (mode) {
|
|
case 0:
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
return d;
|
|
case 1:
|
|
return this.x;
|
|
case 2:
|
|
return (this.y << 8) | d;
|
|
case 3:
|
|
return (this.y << 8) | this.x;
|
|
case 7:
|
|
let addr = (this.y << 8) | this.x;
|
|
this.x = (this.x + 1) & 0xff;
|
|
return addr;
|
|
}
|
|
}
|
|
|
|
|
|
/** calculate a branch page offset
|
|
* @param {number} bus
|
|
* @param {number} d
|
|
* @return {number} page offset
|
|
*/
|
|
offset(bus, d) {
|
|
switch (bus) {
|
|
case 0:
|
|
return d;
|
|
case 1:
|
|
// RAM always has at least 1 page, so no need to mask address
|
|
return this.ram[d];
|
|
case 2:
|
|
return this.ac;
|
|
case 3:
|
|
return this.inReg;
|
|
}
|
|
}
|
|
}
|