189 lines
5.8 KiB
JavaScript
Executable File
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;
|
|
}
|
|
|
|
}
|