8
\$\begingroup\$

I am still working on A Z80 CPU emulator and I have decided to prototype it in javascript and then translate it into a faster language, It might be ported to C but more likely is Java - this means I cannot use pointers or jump-tables. It both compiles assembly and interprets it however both are giant nested switch-case statements with over 200 cases because I cant think of a more elegant way, is this the best way?

This is also the format of all emulator's source code I can find, one giant nested switch-case!

Here is an example of one tiny part of the compiler:

                switch(opcode) {
                    case "NOP":
                        RAM[byte++] = 0x00;
                        break;
                    case "HALT":
                        RAM[byte++] = 0x10;
                        break;
                    case "LD":
                        switch (parts[1]) {
                            case "A":
                                switch (parts[2]) {
                                    case "A":
                                        RAM[byte++] = 0x7F; break;
                                    case "B":
                                        RAM[byte++] = 0x78; break;
                                    case "C":
                                        RAM[byte++] = 0x81; break;
                                    case "D":
                                        RAM[byte++] = 0x82; break;
                                    case "E":
                                        RAM[byte++] = 0x83; break;
                                    case "H":
                                        RAM[byte++] = 0x84; break;
                                    case "L":
                                        RAM[byte++] = 0x85; break;
                                    // LD A, n
                                    default:
                                        RAM[byte++] = 0x3E; 
                                        RAM[byte++] = parseInt(parts[2]);
                                }
                                break;
                            case "B":
                                switch (parts[2]) {
                                    case "A":
                                        RAM[byte++] = 0x47; break;
                                    case "B":
                                        RAM[byte++] = 0x40; break;
                                    case "C":
                                        RAM[byte++] = 0x41; break;
                                    case "D":
                                        RAM[byte++] = 0x42; break;
                                    case "E":
                                        RAM[byte++] = 0x43; break;
                                    case "H":
                                        RAM[byte++] = 0x44; break;
                                    case "L":
                                        RAM[byte++] = 0x45; break;
                                    // LD B, n
                                    default:
                                        RAM[byte++] = 0x06;
                                        RAM[byte++] = parseInt(parts[2]);
                                }
                                break;
                            case "C":
                            ...

For some of these opcodes there is a format of the bits making up the number however I think it is more readable, reliable, faster and smaller to hard-code them all.

This is part of the interpreter:

    switch(RAM[PC++]) {
                // NOP
                case 0x00:
                    break;

                // STOP
                case 0x10:
                    terminate();
                    break;

                // LD A, A
                case 0x7F:
                    // Pointless
                    break;
                // LD A, B
                case 0x78:
                    Registers[0] = Registers[1]; break;
                // LD A, C
                case 0x79:
                    Registers[0] = Registers[2]; break;
                // LD A, D
                case 0x7A:
                    Registers[0] = Registers[3]; break;
                ...

Bytes are both wrote and read in hex because it has more of a pattern than decimal, plus ease of use with a hex editor.

\$\endgroup\$
  • 3
    \$\begingroup\$ You say you ultimately want to implement this in C or Java, which makes this tricky to answer. I have some ideas but they are solely for a JavaScript version, and they won't translate well to those other languages. I'll be happy to post an answer, but if what you want is actually a different language, I'd say you should simply prototype in that other language directly \$\endgroup\$ – Flambino Mar 17 '14 at 0:47
2
\$\begingroup\$

I cant think of a more elegant way, is this the best way?

It's quite a good way: it's clear; and it's simple, so perhaps it's easily extended when you have a complicated bit.

The copy-and-paste duplication in the compiler is the repeated RAM[byte++] = expression. You might get around this with a subroutine ...

switch(opcode) {
    case "NOP":
        return [ 0x00 ]; // return an array of bytes
        break;
    case "HALT":
        ... etc ...

... so that the caller invokes the subroutine, gets an array of bytes back, and writes the array of bytes into RAM (so there's only one statement which writes to RAM).

Another possibility is to define the whole switch statement using a JSON dictionary:

{
    { "NOP": 0x00 },
    { "HALT": 0x10 },
    { "LD": {
        { "A": {
            { "A": 0x7f },
            { "B": 0x78 },
            ... etc ...
            { "!default!": [ 0x06, "parseInt(2)" ]

That's theoretically good:

  • Separates data from the code (perhaps you can define a compiler for a different assembly language just by using a different dictionary)
  • Fewer lines of code (enough to load and then read the dictionary)

Whether this is faster (at run-time) than a switch statement may depend on the JavaScript engine: according to this article some do and some don't make a dictionary faster than a switch statement.

The JSON seems able to deal with special cases, but not very elegantly: for example I used strings with special meanings, "!default!" (I wondered about using null instead) and "parseInt(2)" (could that be a named function instead?).

The interpreter can also use a dictionary of opcode values. Javascript and C both support function pointers (as the dictionary or array/jump-table values).


"however the compiler"

Yes. I only reviewed the code you posted. :-) You are right: that's what I meant (one of the complications I was thinking of) in my first sentence when I said, "it's easily extended when you have a complicated bit".

Still, one possibility is for the subroutine to return several types of data simultaneously, as different properties of a returned object, e.g.: 1) an array of bytes 2) and whether it's a jmp, call, or label.

Another possibility is to have more than one parser: one which returned encoded bytes for every opcode, and another (much shorter) with special-case handling for the relatively few branch and label instructions.

A third possibility is to pass one or more callback functions as parameters into the subroutine: a callback function which the subroutine should call when it detects a jump or call.

\$\endgroup\$
  • \$\begingroup\$ Nice idea of returning byte arrays however the compiler sometimes needs to make note of certain instructions (jmp, call) to come back to, which means the switch-case replacement needs to be able to do instructions. I don't know about JSON dictionaries (and will look into them) but wouldn't this conclusion include it too if it can only store values?.. otherwise it'll have to be a function table which is messier. \$\endgroup\$ – Lee Allan Mar 17 '14 at 18:29
  • \$\begingroup\$ I edited my answer to try to address your comment \$\endgroup\$ – ChrisW Mar 17 '14 at 18:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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