gigatron/rom/Contrib/lb3361/runjs/html/gigatron.js
2025-01-28 19:17:01 +03:00

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;
}
}
}