I've started to work on a JavaScript library called vCPU with the purpose of providing a framework for emulators. vCPU is designed to be able to emulate multiple types of CPUs, with setup only requiring opcodes, memory, creating registers, and sending the clock signal - thus helping save time by eliminating the need to write an entire CPU emulator from scratch. I've created a virtual CPU with the purpose of reading music from memory and writing to an output (in this case sine wave synth), and have made sure that vCPU works properly.
However, I was wondering if anybody could look over the code and see if it is actually ready to be released to the public.
https://github.com/themirrazz/vCPU
The files that need to be looked over are /browser/vcpu.js
and /node/vcpu-lib.js
. You can test it out by pasting one /browser/vcpu.js
and /demo/vMusic/vMusic-Demo.js
into the JavaScript console of your browser (if needed tested in Chrome)
I embedded the relevant code. Also just to clarify, this code does work; I have tested it myself, and it works fine.
// vCPU 0.0.1 Stable Release
// (c) themirrazz 2024
var vCPU = (function () {
var vCPUEvent = function () {
this.trusted = true;
this.cancelBubbles = false;
this.cancelable = false;
this.address = 0x00;
this.value = 0x00;
this.error = null;
};
vCPUEvent.prototype.toString = function () {
return '[object vCPUEvent]';
}
vCPUEvent.prototype.preventDefault = function () {
// don't. break. anything.
return 'やった';
};
vCPUEvent.prototype.stopPropagation = function () {
// don't. break. anything.
return 'やった';
};
var vRegister = function () {
this.data = 0x00;
};
vRegister.prototype.get = function () {
return this.data;
};
vRegister.prototype.set = function (data) {
return this.data = data;
};
var vCPU = function (bits, addrBits) {
// if they don't pick we like 16 bits
if(!bits) {
bits = 16;
}
if(typeof bits != 'number' || bits < 1 || bits !== Math.floor(bits)) {
throw new TypeError("the amount of bits must be an integer number that is greater than 0");
}
this.bits = bits;
if(!addrBits) {
addrBits = bits;
}
if(typeof addrBits != 'number' || addrBits < 1 || addrBits !== Math.floor(addrBits)) {
throw new TypeError("the amount of bits must be an integer number that is greater than 0");
}
this.addressLines = addrBits;
// initalize the program counter, aka 'pointer'
this.pointer = 0;
this.GPRegisters = [];
this.Flags = {
Carry: 0
};
this.StackPointer = addrBits - 1;
// event handlers
this.__events__ = {};
// onmemoryread and onmemorywrite events
this.onmemoryread = function () {
return 0x00;
};
this.onmemorywrite = function () {
// do nothing by default
};
// prepare for adding opcodes
this.opcodes = {};
// our special register;
this.SpecialRegister = new vRegister(8);
};
vCPU.prototype.incrementStackPointer = function () {
if(this.StackPointer < this.addressLines-1) {
this.StackPointer++
} else {
this.StackPointer = 0
}
};
vCPU.prototype.decreaseStackPointer = function () {
if(this.StackPointer > 0) {
this.StackPointer--
} else {
this.StackPointer = this.addressLines - 1;
}
};
vCPU.prototype.addEventListener = function(event, handle) {
if(!this.__events__[event]) {
this.__events__[event] = [];
}
this.__events__[event].push(handle);
};
vCPU.prototype.removeEventListener = function(event, handle) {
if(!this.__events__[event]) {return false}
var kn = this.__events__[event];
var qk = [];
for(var i = 0; i < kn.length; i++) {
if(kn[i]!=handle) {qk.push(kn[i])}
}
this.__events__[event] = qk;
return true
};
vCPU.prototype.__OnEvent__ = function (event, data) {
if(!this.__events__[event]) {return false}
for(var i = 0; i < this.__events__[event].length; i++) {
try {
this.__events__[event][i](data);
} catch (e) {
console.error(e);
}
}
}
vCPU.prototype.addRegister = function (bits) {
this.GPRegisters.push(new vRegister(bits));
};
vCPU.prototype.setOpcode = function (opcode, type) {
this.opcodes[opcode] = type;
};
vCPU.prototype.clock = function () {
if(this.pointer >= Math.pow(2,this.addressLines)) {
// how the hell did that happen?
this.pointer = 0;
}
var self = this;
var cb = this.comebackOpcode;
var mr = new vCPUEvent();
mr.address = this.pointer;
var $value = this.onmemoryread(mr)
var op = cb || this.opcodes[$value];
if(!op) {
op = function () {};
}
if(cb) {
// prevent harmful infinite looping
this.comebackOpcode = undefined;
}
try {
op({
ReadMemory: function (addr) {
var mr = new vCPUEvent();
mr.address = addr;
return self.onmemoryread(mr)
},
WriteMemory: function (addr, value) {
var mw = new vCPUEvent();
mw.address = addr;
mw.value = value;
return self.onmemorywrite(mw);
},
SetComebackFunction: function (func) {
return self.comebackOpcode = func;
},
Registers: self.GPRegisters,
GetStackPointer: function () {
return self.StackPointer
},
IncreaseStackPointer: function () {
return self.incrementStackPointer()
},
DecreaseStackPointer: function () {
return self.decreaseStackPointer()
},
SetPointer: function (d) {
// it will automatically get incremented 1 more
d = d - 1;
if(d < 0) {
// this is as low as you can go
self.pointer = -1;
} else {
while(d > Math.pow(2,self.bits)) {
// no memory leaks, okay!
d = d - Math.pow(2,self.bits);
}
if(d < 0) {
// I legit don't know how that happened
self.pointer = -1;
} else {
self.pointer = d;
}
}
},
GetPointer: function (d) {
return self.pointer;
},
ToHex: function (n) {
if(Math.floor(n)!==n || n < 0 || n > 255) {
throw new TypeError();
}
var hex = n.toString(16);
if(hex.length === 1) {
return '0'+hex;
} else {
return hex;
}
},
SplitHex: function (n) {
// split hex numbers apart into groups of one byte
// very useful function indeed
if(Math.floor(n)!==n || n < 0) {
throw new TypeError();
}
var hex = n.toString(16);
if(Math.floor(hex/2)!==hex/2) {
// only time 0 is omitted is beginning
hex = "0"+hex;
}
var arr = [];
var d = '';
for(var i = 0; i < hex.length; i++) {
if(Math.floor(i/2) === i/2) {
d = hex[i];
} else {
arr.push(d+hex[i]);
}
}
return arr;
},
Data: $value,
ToBinary: function (n) {
var d = n.toString('2');
if(d.length === 1) {
return "0000000"+d;
} else if(d.length === 2) {
return "000000"+d;
} else if(d.length === 3) {
return "00000"+d;
} else if(d.length === 4) {
return "0000"+d;
} else if(d.length === 5) {
return "000"+d;
} else if(d.length === 6) {
return "00"+d;
} else if(d.length === 7) {
return "0"+d;
}
return d;
},
BitShiftLeft: function (n) {
return n[1]+n[2]+n[3]+n[4]+n[5]+n[6]+n[7]+n[0]
},
BitShiftRight: function (n) {
return n+[7]+n[0]+n[1]+n[2]+n[3]+n[4]+n[5]+n[6]
},
BitShiftLeftCarry: function (n, c) {
return {
bits: n[1]+n[2]+n[3]+n[4]+n[5]+n[6]+n[7]+c,
carry: n[0]
}
},
BitShiftRightCarry: function (n, c) {
return {
bits: c+n[0]+n[1]+n[2]+n[3]+n[4]+n[5]+n[6],
carry: n[7]
}
},
Flags: function () {
return self.Flags;
}
});
} catch (error) {
// the manufacturing company did a bad job
this.clock = function () {
throw new TypeError("'clock' cannot be called on type 'FkedUpVirtualCPU', buy a new one");
}
console.error(error);
var event = new vCPUEvent();
event.error = error;
// tell the whole damn world
this.__OnEvent__('fatalerror', event);
try {
vCPU.onfatalerror(event)
} catch (yetAnotherError) {
// does it matter? the cpu's already fried www
return;
}
}
this.pointer ++;
if(this.pointer > Math.pow(2,this.addressLines)) {
this.pointer = 0;
}
};
return vCPU;
})();
-
3\$\begingroup\$ The code is working, but thanks for letting me know about embedding the relevant code! \$\endgroup\$themirrazz– themirrazz2024年04月16日 19:54:35 +00:00Commented Apr 16, 2024 at 19:54
-
3\$\begingroup\$ There is a great deal of prior art in this space. Perhaps there is some reference you would like to cite? Or at least draw a comparison between MIPS or risc V or another processor that you are closely aligned with? Are we offering support for low instruction decode complexity, compressed machine code, or some other goal? \$\endgroup\$J_H– J_H2024年04月16日 20:53:25 +00:00Commented Apr 16, 2024 at 20:53
-
\$\begingroup\$ @J_H Well it's not one processor. This library is designed to allow you to emulate any CPU you want to. It handles the clocking process, and all you have to do is program in the opcode. I'm also going to try and make some pre-setup opcodes you can get from the GitHub repository (github.dev/themirrazz/vCPU). It's designed to work in browsers, including IE, as well as Node.JS, and I even started a very experimental port to the Micro:bit (though is not finished enough to work yet). The goal is to simplify having to program an entire CPU emulator from scratch. \$\endgroup\$themirrazz– themirrazz2024年05月03日 14:30:43 +00:00Commented May 3, 2024 at 14:30
1 Answer 1
What the..
You have the following
if(this.pointer >= Math.pow(2,this.addressLines)) { // how the hell did that happen? this.pointer = 0; }
It is the comment that is disturbing and indicates that there is a serious misunderstanding of how your code works.
How often does this "..the hell..." happen, and with that WHY!!! does it happen, then you should track down where it happens and then fix it!
Or if maybe just mask the pointer as would happen at the hardware level
this.pointer &= (1 << this.addressLines) - 1; // mask out bits from this.addressLines
Old school
The code is very old school javascript (pre ECMAScript5). Not to say that by pre 2015 standards its not bad code (ignoring a few minor points).
You should consider catching up on modern javascript as it offers many improvements, short cuts, and under the hood optimisations.
Example arrow function does not need to have copy of
this
No need for theself
op({ ReadMemory: function (addr) { var mr = new vCPUEvent(); mr.address = addr; return self.onmemoryread(mr) /* BM67 where is the ; */ },
Can be
op({
ReadMemory: addr => { // Arrow function
const mr = new vCPUEvent(); // As constant
mr.address = addr;
return this.onmemoryread(mr); // Uses this
},
Example The class syntax lets you avoid the need to assign to the prototype
var vCPUEvent = function () { /* ... other stuff */ vCPUEvent.prototype.toString = function () { return '[object vCPUEvent]'; }
would be just
class vCPUEvent { // ... other stuff toString() { return '[object vCPUEvent]'; }
There are many more changes if you use modern JS.
-
\$\begingroup\$ Okay, thanks for your feedback! Here is some clarification on somethings in my code. 1. The "the hell" that happens is if an opcode attempts to set the pointer to a value that is not possible. (This library does not include built-in opcodes.) 2. The reason it uses pre-ES5 code is to include compatibility. This program is intended to be compatible with multiple platforms that use JavaScript. This includes older browsers, such as Internet Explorer. (Yes, I still believe Internet Explorer has potential today - please don't judge me). \$\endgroup\$themirrazz– themirrazz2024年05月03日 14:35:48 +00:00Commented May 3, 2024 at 14:35