loading...
All Defender articles | Back to top

Defender Audio Hardware Emulator

The Motorola 68000

Tanglewood

The 1981 arcade classic Defender has a very unique sound. This page will analyze how the sound hardware and software is engineered.

Emulator

After studying the MC6802 assembly code and retrieving the Motorola M6800 Programming Reference Manual, it was feasible to write an emulator of the Defender sound hardware.


The MC6802 is implemented in a very straightforward CPU class:
class CPU
{
    // flags
    static const unsigned char C = 0x01;
    static const unsigned char I = 0x10;
    ...

    // registers
    BYTE a, b, cc;
    WORD x, pc, sp;

    // memory (ROM and RAM)
    BYTE *mem;

    // clock cycles elapsed
    uint64_t cycles = 0;

   ...
};

There is a step method that gets the opcode from the Program Counter (PC) and has a giant switch that implements the different instructions based on their opcodes:

// Get memory value at 'addr'
BYTE get8(WORD addr)
{
    SDL_assert(addr < 0x10000);
    ...
    return mem[addr];
}
bool step()
{
    // get opcode from PC address
    unsigned char op = get8(pc);

    ...

    switch (op)
    {
    case 0x01:                     ++pc; c(2); break; // nop
    case 0x07: b = lda(cc);        ++pc; c(2); break; // tab
    case 0x08: x = inx(x);         ++pc; c(4); break; // inx
    case 0x09: x = dex(x);         ++pc; c(4); break; // dex
    case 0x0e: set_flag(I, false); ++pc; c(2); break; // cli
    case 0x0c: set_flag(C, false); ++pc; c(2); break; // clc
    case 0x0f: set_flag(I, true);  ++pc; c(2); break; // sei
    case 0x10: a = sub(a, b);      ++pc; c(2); break; // sba
    case 0x16: b = lda(a);         ++pc; c(2); break; // tab
    case 0x17: a = lda(b);         ++pc; c(2); break; // tba
    case 0x1b: a = add(a, b);      ++pc; c(2); break; // aba

    ...

    }
}

The simplest instruction is NOP, which does nothing and takes two CPU cycles to execute. We just increment PC to get the next instruction and increment the CPU cycle count with 2:

bool step()
{
    ...
    switch(op)
    {
    case 0x01:
	++pc;  // next instruction
	c(2);  // increment CPU cycles
	break; // nop
    ...
    }
}

void c(int dcycle)
{
    cycles += dcycle;
}

The LDA instruction loads a value into the accumulator (register A). The implementation is trivial, save for updating the Z and N flags, indicating whether the value is 0 or negative:

BYTE lda(BYTE m)
{
    set_flag(Z, m == 0);   // update zero flag
    set_flag(N, m & 0x80); // update negative flag
    return m;              // we don't actually do anything except return the value
}
void set_flag(unsigned char flag, bool value)
{
    cc &= ~flag;           // set flag = 0
    if (value) cc |= flag; // set flag bit if value is true
}


Original Defender ROM emulation test

References