Emulating a computer
- Emulate the CPU
- Emulate the video
- Emulate the sound
- ...
- Profit?
Emulating a Z80
- CISC chip - 900+ instruction
- IO bus
- 3.53MHz
- 4 cycles (1µs) minimum
- 7 cycles load or add
Emulating a Z80
- 18 8-bit registers
- A, B, C, D, E, H, L
- A', B', C', D', E', H', L'
- Flags, IRQs, RAM refresh
- Pairable: AF, BC, DE, HL
- 4 16-bit registers
Z80 - Example
21 2a 06 LD HL,0x062a ; HL = 0x062a
87 ADD A,A ; A = 2*A
5f LD E,A
16 00 LD D,0x00 ; DE = (u16)A
19 ADD HL,DE ; HL = HL + DE
7e LD A,(HL) ; A = *(u8*)HL;
23 INC HL ; HL++
66 LD H,(HL) ; A = *(u8*)HL;
6f LD L,A ; L = A
e9 JP HL ; jump to HL
Z80 - Decoding
- Optional prefix byte (
cb
, dd
, ed
or fd
)
- One byte opcode
- 0, 1 or 2 operand bytes
af XOR A, A
3e ff LD A, 0xff
46 LD B, (HL)
dd 46 0f LD B, (IX + 0x0f)
5b LD E, E
ed 5b 19 d0 LD DE, (0xd019)
Z80 - Executing
const opcode = readbyte(z80.pc++);
switch (opcode) {
case 0xaf: // XOR A, A
z80.a ^= z80.a;
break;
case 0x3e: // LD A, constant
z80.a = readbyte(z80.pc++);
break;
case 0x46: // LD B, (HL)
z80.b = readbyte((z80.h<<8) | z80.l);
break;
// and so on for all the other instructions
case 0xdd:
return handle_prefix_dd();
// ... etc
… but there's more
Z80 - Executing
- Flags:
- carry, half-carry
- zero, sign
- overflow, parity
- IRQs
- Input/output
Z80 - Executing
Z80 - JSSpeccy
- Perl -> C
- C -> Javascript
- Avoids very repetitive code:
- 938 instructions
- 1242 lines of perl
- 5400+ lines of Javascript
ADD A, imm
const sz53_table[256], hc_add_table[8], oflo_add_table[8] = // ... computed once
function add_a_imm() {
const c = readbyte(z80.pc++);
const result = z80.a + c;
const lookup = ((z80.a & 0x88) >> 3) | ((c & 0x88) >> 2) | ((result & 0x88) >> 1);
z80.f = (result & 0x100 ? C_FLAG : 0) // Carry
| hc_add_table[lookup & 0x07] // 1/2 carry
| oflo_add_table[lookup >> 4] // overflow
| sz53_table[z80.a]; // sign, zero, “undef” bits
z80.a = result & 0xff;
clock += 7;
}
Memory Map
fffc-ffff |
Paging registers |
e000-fffb |
Mirror of 8KB RAM |
c000-dfff |
8KB RAM |
8000-bfff |
16KB ROM page 2 (or Cartridge RAM) |
4000-7fff |
16KB ROM page 1 |
0400-3fff |
15KB ROM page 0 |
0000-03ff |
1KB ROM bank 0 |
ffff |
Page 2 ROM |
fffe |
Page 1 ROM |
fffd |
Page 0 ROM |
fffc |
ROM/RAM select |
Emulating Memory
const romBanks = [new Uint8Array(16384), ...]; // 16KB blocks from ROM files
const pages = [0, 1, 2]; // Paging registers
const ram = new Uint8Array(8192); // 8KB of on-board RAM
function readByte(address) {
const offset = address & 0x3fff; // offset withing a 16KB page
if (address < 0xc000) { // It's ROM: which page?
const page = (address < 0x400) ? 0
: pages[address >>> 14];
return romBanks[page][offset];
}
// RAM, but only 8K's worth.
return ram[offset & 0x1fff]);
}
Emulating Memory
function writeByte(address, byte) {
if (address < 0xc000)
return; // It's ROM!
if (address === 0xfffc)
/*handle rom/ram*/;
else if (address >= 0xfffd)
pages[address - 0xfffd] = byte;
else ram[address & 0x1fff] = byte;
}