cpu-simulator/simulator/sim.js

189 lines
5.8 KiB
JavaScript
Executable File

class eduProc {
reset() {
// initialise instruction and data memory
this.memory = new Array(65536).fill(0);
this.state = 0; // 0=fetch, 1=decode, 2=execute;
// store the registers as private members
this.programCounter = 0x0000; // 16 bits
this.instructionRegister = 0x0000; // 16 bits
this.memoryAddressRegister = 0x0000; // 16 bits
//memoryDataRegister = 0x00; // 8 bits
this.flags = 0x0; // 4 bits
this.flagNames = { input: 4, overflow: 3, negative: 2, carry: 1, zero: 0 }
this.registers = [0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00]; //8 bits
// human friendly names for the four special purpose registers
this.regNames = { instruction_page: 0, data_page: 1, stack_pointer: 2, serial_io: 3 };
this.stack_page = 0xFF;
}
freeze() {
// todo
}
constructor() {
reset();
}
// handle flags
setFlag(flagName, onoff) {
// get bit position of flag
const bitPosition = this.flagNames[flagName];
// ensure we have a boolean
onoff = onoff ? 1 : 0;
// clear the bit in the bitmask, so we can replace it with the provided value
const clearMask = ~(1<<bitPosition);
//update bit and update flag register
this.flags = (this.flags & clearMask) | (onoff << bitPosition);
}
getBit(value, bitPosition) {
return(value & (1<<bitPosition))==0?0:1
}
getFlag(flagName) {
return this.getBit(self.flags,flagNames[flagName]);
}
ALU(opcode,dest,operand_2){
operand_1 = this.registers[dest];
// perform operation
switch(opcode){
case 0:
// add
result = operand_1 + operand_2;
break;
case 1:
//sub
result = operand_1 - operand_2;
break;
case 2:
// nand
result = ~(operand_1 & operand_2);
break;
case 3:
// xor
result = operand_1 ^ operand_2;
break;
case 4:
// adc
result = operand_1 + operand_2 + getFlag("carry");
break;
case 5:
// subb
result = operand_1 - operand_2 - getFlag("carry");
break;
case 6:
// cmp
result = operand_1 - operand_2;
break;
case 7:
// mov
result = operand_2;
break;
}
result = result>=0 ? result % 255 : 255+result+1; // overflow if positive, underflow if negative
// write result and flags
if(operand!=6) { // don't write result of cmp
this.registers[dest] = result;
}
if(opcode==7) { // move instruction: don't affect flags
return;
}
this.setFlag("zero",result==0);
this.setFlag("negative",getBit(result,7));
if(opcode==2||opcode==3) { // logic instruction: only zero and negative flag are touched
return;
}
this.setFlag("carry", result>255||result<0);
op1_sign = this.getBit(operand_1,7);
op2_sign = this.getBit(operand_2,7);
result_sign = this.getBit(result,7);
this.setFlag("overflow",~(op1_sign^op2_sign) && (op1_sign^result_sign)); // sets flag if two inputs have same sign but output has different
}
// load a new program into memory and reset the processor
loadProgram(bytecode) {
reset();
this.memory.splice(0,bytecode.length, ...bytecode);
freeze();
}
// FETCH STAGE: load instruction from memory and increment PC
fetch(stage=0) {
if (stage=0)
this.instructionRegister = this.memory[this.programCounter]<<8;
else
this.instructionRegister += this.memory[this.programCounter];
this.programCounter = (this.programCounter+1)%65535;
}
execute() {
instruction = this.instructionRegister;
opcode = instruction>>12;
addressingMode = this.getBit(instruction, 4); // 0 = from reg, 1 = immediate
operand_1 = (instruction>>8)&0x7 // extract operand 1
operand_2 = instruction&0xFF // extract second byte
if(!addressingMode) { // not immediate data - load from register instead
operand_2 = this.registers[operand_2>>5];
}
if(opcode<8) { // this means ALU INSTRUCTION
this.ALU(opcode,operand_1,operand_2);
} else {
data_address = this.regNames["data_page"]<<8+operand_2;
branch_address = this.regNames["instruction_page"]<<8+operand_2;
stack_pointer = this.stack_page+this.regNames["stack_pointer"];
switch(opcode){
case 8: // load
this.registers[operand_1] = this.memory[data_address];
break;
case 9: //store
this.memory[data_address] = this.registers[operand_1];
break;
case 10: // stack
if(!addressingMode){ // push
this.memory[stack_pointer] = this.registers[operand_1];
this.regNames["stack_pointer"] -= 1;
if(this.regNames["stack_pointer"]<0)
this.regNames["stack_pointer"]=255;
} else { // pop
this.registers[operand_1]=this.memory[stack_pointer];
this.regNames["stack_pointer"]=(this.regNames["stack_pointer"]+1)%256
}
break;
case 11: // beqz
if(this.getFlag("zero"))
this.programCounter = branch_address;
break;
case 12: // bneg
if(this.getFlag("negative"))
this.programCounter = branch_address;
break;
case 13: // binput
if(this.getFlag("input"))
this.programCounter = branch_address;
break;
case 14:
if(this.getFlag("overflow"))
this.programCounter = branch_address;
break;
case 15:
this.programCounter = branch_address;
break;
}
}
}
cycle(instructionStep=false) {
if(this.state==0||instructionStep)
this.fetch(0);
if(this.state==1||instructionStep)
this.fetch(1);
if(this.state==2||instructionStep)
this.execute();
this.state = instructionStep ? 0 :(this.state+1)%3;
}
}