3
\$\begingroup\$

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;
})();
\$\endgroup\$
3
  • 3
    \$\begingroup\$ The code is working, but thanks for letting me know about embedding the relevant code! \$\endgroup\$
    – themirrazz
    Commented Apr 16 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
    Commented Apr 16 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
    Commented May 3 at 14:30

1 Answer 1

6
\$\begingroup\$

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 the self

            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.

\$\endgroup\$
1
  • \$\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
    Commented May 3 at 14:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.