Question

I'm trying to write a CPU emulator in C#. The machine's object looks like this:

class Machine 
{
    short a,b,c,d; //these are registers.

    short[] ram=new short[0x10000];  //RAM organised as 65536 16-bit words

    public void tick() { ... } //one instruction is processed
}

When I execute an instruction, I have a switch statement which decides what the result of the instruction will be stored in (either a register or a word of RAM)

I want to be able to do this:

short* resultContainer;

if (destination == register)
{
    switch (resultSymbol) //this is really an opcode, made a char for clarity
    {
       case 'a': resultContainer=&a;
       case 'b': resultContainer=&b;
       //etc
    }
}
else
{
    //must be a place in RAM
    resultContainer = &RAM[location];
}

then, when I've performed the instruction, I can simply store the result like:

*resultContainer = result;

I've been trying to figure out how to do this without upsetting C#.

How do I accomplish this using unsafe{} and fixed(){ } and perhaps other things I'm not aware of?

Était-ce utile?

La solution

This is how I would implement it:

class Machine
{
    short a, b, c, d;
    short[] ram = new short[0x10000];

    enum AddressType
    {
        Register,
        DirectMemory,
    }

    // Gives the address for an operand or for the result.
    // `addressType`and `addrCode` are extracted from instruction opcode
    // `regPointers` and `ramPointer` are fixed pointers to registers and RAM.
    private unsafe short* GetAddress(AddressType addressType, short addrCode, short*[] regPointers, short* ramPointer)
    {
        switch (addressType)
        {
            case AddressType.Register:
                return regPointers[addrCode]; //just an example implementation
            case AddressType.DirectMemory:
                return ramPointer + addrCode; //just an example implementation
            default:
                return null;
        }
    }

    public unsafe void tick()
    {
        fixed (short* ap = &a, bp = &b, cp = &c, dp = &d, ramPointer = ram)
        {
            short*[] regPointers = new short*[] { ap, bp, cp, dp };

            short* pOperand1, pOperand2, pResult;
            AddressType operand1AddrType, operand2AddrType, resultAddrType;
            short operand1AddrCode, operand2AddrCode, resultAddrCode;

            // ... decipher the instruction and extract the address types and codes

            pOperand1 = GetAddress(operand1AddrType, operand1AddrCode, regPointers, ramPointer);
            pOperand2 = GetAddress(operand2AddrType, operand2AddrCode, regPointers, ramPointer);
            pResult = GetAddress(resultAddrType, resultAddrCode, regPointers, ramPointer);

            // execute the instruction, using `*pOperand1` and `*pOperand2`, storing the result in `*pResult`.

        }
    }
}

To get the address of the registers and RAM array, you have to use the fixed statement. Also you can only use the acquired pointers in the fixed block. So you have to pass around the pointers.

Autres conseils

What if we look at the problem from another view? Execute the instruction and store the result in a variable (result), and then decide on where you should put the result. Doesn't work?

A good option, one I had planned for DevKit but not yet implemented, is to generate your emulation code. It's a time-honoured, tried and tested solution. For every opcode/register combination, or some efficient subset, generate the code for ADD A,B and SUB X,Y separately. You can use a bitmask to grab the specific case you're looking for and simply execute the correct code. Compiler should be able to use a jump table under the hood, and there's no lookups, no conditionals, should be pretty efficient.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top