1
votes

Wanting to make sure I won't have to go back and redo large chunks of code... I have each opcode as a value in an enum that implements Runnable. Is there a more efficient way that I should do this or am I on the write track to getting something that runs a testsuite rom accurately?

package com.codeblox.nes.cpu;

public class CPU {

    private byte x, y, ac, pcl, pch;
    private short pc;
    private boolean debugEnabled = false, isGood = true;
    private static byte [] mainMem = new byte [0x10000];
    public Opcode opcode;

    CPU(boolean debugEnabled){
        opcode =Opcode.nop;
        pc = 0;
        this.debugEnabled = debugEnabled;

    }

    public enum Opcode implements Runnable{


        adc(){public void run(){System.out.println("adc");}},
        and(){public void run(){System.out.println("and");}},
        asl(){public void run(){System.out.println("asl");}},
        bcc(){public void run(){System.out.println("bcc");}},
        bcs(){public void run(){System.out.println("bcs");}},
        beq(){public void run(){System.out.println("beq");}},
        bit(){public void run(){System.out.println("bit");}},
        bmi(){public void run(){System.out.println("bmi");}},
        bne(){public void run(){System.out.println("bne");}},
        bpl(){public void run(){System.out.println("bpl");}},
        brk(){public void run(){System.out.println("brk");}},
        bvc(){public void run(){System.out.println("bvc");}},
        bvs(){public void run(){System.out.println("bvs");}},
        clc(){public void run(){System.out.println("clc");}},
        cld(){public void run(){System.out.println("cld");}},
        cli(){public void run(){System.out.println("cli");}},
        clv(){public void run(){System.out.println("clv");}},
        cmp(){public void run(){System.out.println("cmp");}},
        cpx(){public void run(){System.out.println("cpx");}},
        cpy(){public void run(){System.out.println("cpy");}},
        dec(){public void run(){System.out.println("dec");}},
        dex(){public void run(){System.out.println("dex");}},
        dey(){public void run(){System.out.println("dey");}},
        eor(){public void run(){System.out.println("eor");}},
        inc(){public void run(){System.out.println("inc");}},
        inx(){public void run(){System.out.println("inx");}},
        iny(){public void run(){System.out.println("iny");}},
        jmp(){public void run(){System.out.println("jmp");}},
        jsr(){public void run(){System.out.println("jsr");}},
        lda(){public void run(){System.out.println("lda");}},
        ldx(){public void run(){System.out.println("ldx");}},
        ldy(){public void run(){System.out.println("ldy");}},
        lsr(){public void run(){System.out.println("lsr");}},
        nop(){public void run(){System.out.println("nop");}},
        ora(){public void run(){System.out.println("ora");}},
        pha(){public void run(){System.out.println("pha");}},
        php(){public void run(){System.out.println("php");}},
        pla(){public void run(){System.out.println("pla");}},
        plp(){public void run(){System.out.println("plp");}},
        rol(){public void run(){System.out.println("rol");}},
        ror(){public void run(){System.out.println("ror");}},
        rti(){public void run(){System.out.println("rti");}},
        rts(){public void run(){System.out.println("rts");}},
        sbc(){public void run(){System.out.println("sbc");}},
        sec(){public void run(){System.out.println("sec");}},
        sed(){public void run(){System.out.println("sed");}},
        sei(){public void run(){System.out.println("sei");}},
        sta(){public void run(){System.out.println("sta");}},
        stx(){public void run(){System.out.println("stx");}},
        sty(){public void run(){System.out.println("sty");}},
        tax(){public void run(){System.out.println("tax");}},
        tay(){public void run(){System.out.println("tay");}},
        tsx(){public void run(){System.out.println("tsx");}},
        txa(){public void run(){System.out.println("txa");}},
        txs(){public void run(){System.out.println("txs");}},
        tya(){public void run(){System.out.println("tya");}},
        ;

        public String mnemonic = "";
        public String addressMode;
        public byte code;
        public byte data;

        Opcode(){

            this.mnemonic = new String();

        }

        public void print(){

            System.out.printf("Opcode: %02X %s %s\n", 
                              this.code, 
                              this.mnemonic.toUpperCase(),
                              this.addressMode);

        }

        public String getMode00(byte opcode){

            switch(opcode){

                case 0x00: return "Immediate";
                case 0x04: return "ZeroPaged";
                case 0x0C: return "Absolute";
                case 0x14: return "IndexedZeroPagedX";
                case 0x1C: return "IndexedAbsoluteX";
                default: return "Type 0 undefined";

            }

        }

        public String getMode01(byte opcode){

            switch(opcode){

                case 0x00: return "InirectIndexedZeroPagedX";
                case 0x04: return "ZeroPaged";
                case 0x08: return "Immediate";
                case 0x0C: return "Absolute";
                case 0x10: return "IndrectedZeroPagedY";
                case 0x14: return "IndexedZeroPagedX";
                case 0x18: return "IndexedAbsoluteY";
                case 0x1C: return "IndexedAbsoluteX";
                default: return "Type 1 Undefined";         

            }

        }

        public String getMode02(byte opcode){ 

            switch(opcode){

                case 0x00: return "Immediate";
                case 0x04: return "ZeroPaged";
                case 0x08: return "Accumulator";
                case 0x0C: return "Absolute";
                case 0x14: return "IndexedZeroPagedX";
                case 0x1C: return "IndexedAbsoluteX";
                default: return "Type 2 Undefined";

            }

        }

        public String getMode03(byte opcode){ return "";}

        public void decode(){

            switch(this.code & 0x03){

                case 0x00: this.addressMode = getMode00((byte)(this.code & 0x1C)); break;
                case 0x01: this.addressMode = getMode01((byte)(this.code & 0x1C)); break;
                case 0x02: this.addressMode = getMode02((byte)(this.code & 0x1C)); break;
                case 0x03: this.addressMode = getMode03((byte)(this.code & 0x1C)); break;
                default: break;


            }


        }

    }


    public void init(){

        pc = 0;

    }

    public void start(){

        while(isGood){


            opcode.code = readMem(pc++);
            CPU.Opcode.valueOf(opcode.mnemonic).run();

        }

        if(!isGood){

            System.err.println("isGood == false");

        }

    }

    public byte readMem(short ptr){

        return mainMem[ptr];

    }

    public byte readMem(short ptr, byte addressMode){

        return mainMem[ptr];

    }

    public void exec(){

        opcode.decode();

        switch(opcode.code & 0xFF){

            case 0x69: case 0x65: case 0x75: 
            case 0x6D: case 0x7D: case 0x79: 
            case 0x61: case 0x71: opcode.mnemonic = "adc"; break;

            case 0x29: case 0x25: case 0x35: 
            case 0x2D: case 0x3D: case 0x39:
            case 0x21: case 0x31: opcode.mnemonic = "and"; break;

            case 0x0A: case 0x06: case 0x16: 
            case 0x0E: case 0x1E: opcode.mnemonic = "asl"; break;



            default: opcode.mnemonic = null;

        }

        //Opcodes.valueOf(this.mnemonic).run();

    }

    public void testOpcodes(){

        opcode.code = 0;

        while((opcode.code & 0xFF) < 0xFF){

            //System.out.printf("PC = 0x%04X \n", PC);
            exec();
            if(opcode.mnemonic != null)
                opcode.print();
                //Opcode.valueOf(opcode.mnemonic).run();

            opcode.code++;

        }


    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        CPU cpu = new CPU(true);
        cpu.init();
        cpu.testOpcodes();

    }

}
2
I' say that the Runnable interface is the wrong choice. Your OpCodes must fetch operands (if they have), change registers in the cpu and change the Program pointer, Therfore the method to implement must have (at least) a cpu , a RAM object and the current Program pointer as parameters and return the new ProgramPointer.Timothy Truckle
Code review questions should be posted on the codereview site.Kayaman
I tried moving it to codereview but there is a cooldown timer that requires one to wait 40 minutes between posts. Sorry about that. Timothy - I could make memory and each register static. That way, they are accessible from anywhere within my project. Otherwise, I may just move this from an enum to it's own class with an argument for each opcode that references the CPU.Codeblox
Add an abstract function to your enum as replacement for Runnable/run(). Allows your own parameters.brummfondel
Sure; in a complete system the 6502 and the other chips — e.g. the NES PPU in this case — act in parallel. If your 6502 emulator does a complete operation of 2–7 cycles as a single thing, then you run the PPU for 2–7 cycles as a single thing, then repeat, then technically you've started with an inaccuracy. It's one a lot of emulators don't care about, and one you may decide not to care about, but it's the difference between those that are enthusiastically cycle accurate and those that are merely close. Grab 64doc.txt to see what the 6502 does every single cycle if you're interested.Tommy

2 Answers

2
votes

Well, I think that's the start of a good way to write a 6502 CPU emulator. But it needs some work ...

Ask yourself: What is an enum in Java? And what is it good for?

It's basically a class with a fixed number of instances, so it's great for representing static data (and behaviour) and grouping that - with associated methods - to be easily visible, testable, modifiable.

In various methods you have switch statements that break out the different addressing modes and operations for each opcode:

switch(opcode) {
    case 0x00: return "Immediate";
    case 0x04: return "ZeroPaged";
    case 0x0C: return "Absolute";
    case 0x14: return "IndexedZeroPagedX";
    case 0x1C: return "IndexedAbsoluteX";
    default: return "Type 0 undefined";
}

And we would have to add more switch statements if we wanted instructions times etc.

But this is static data. Those cases constants should be enum properties. Isn't this the kind of data that should be encoded in the enum? I think so, and so did Brendan Robert, who wrote JACE, the Java Apple Computer Emulator. His code is a great example of a well thought-out Java enum.

Here are the first few lines of his 6502 CPU's OPCODE enum:

public enum OPCODE {
    ADC_IMM(0x0069, COMMAND.ADC, MODE.IMMEDIATE, 2),
    ADC_ZP(0x0065, COMMAND.ADC, MODE.ZEROPAGE, 3),
    ADC_ZP_X(0x0075, COMMAND.ADC, MODE.ZEROPAGE_X, 4),
    // ...
}

All the static data is grouped together nicely, easily visible, ready to be used in case statements etc.

0
votes

I can't speak best or worst, I can only speak to what I did.

I have an OpCode class, and I create an instance of this class for each opcode (0-255, undefined opcodes are NOPs on my machine).

My OpCode contains two methods. One represents the addressing mode, the other the actual instruction.

Here's my execute method:

public void execute(CPU cpu) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    Integer value = (Integer) addrMethod.invoke(cpu);
    opMethod.invoke(cpu, value);
}

I build up my list of OpCodes with a list of strings, such as ORA abs. ORA is mapped to a logic method in my CPU class, and abs is mapped to another addressing method. I use reflecting to lookup the methods and stuff them in to my OpCode instances.

public void ORA(int value) {
    acc = acc | value;
    setFlagsNZ(acc);
}

public int fetchAbsolute() {
    int addr = addrAbsolute();
    return fetchByte(addr);
}

addrAbsolute will pull the 2 bytes from memory, and increment the PC by 2, among other things. fetchByte gets the value at the address. The value is then passed in to ORA, which acts on the accumulator.

In the end, I have 256 opcodes with a method for the logic, and a method for the addressing.

The core of my simulator is simply setting the initial address, fetching the opcode from that address, increment the address, the execute the opcode.

int code = mem.fetchByte(pc++);
OpCode op = Instructions.opCodes[code];
op.execute(this);

(this being the CPU instance).

Mind, mine is a soft simulator, it doesn't strive for cycle parity or anything like that. It doesn't simulate any specific hardware (like a C64). It's a raw 6502 with a few dedicated memory locations that I/O to a terminal.

But this is what came out of my little head. I didn't study other simulators, I wasn't motivated to go sussing out bit patterns within the instructions. I just make a table for every possible opcode and what it was supposed to do.