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
87 ADD A,A
5f LD E,A
16 00 LD D,0x00
19 ADD HL,DE
7e LD A,(HL)
23 INC HL
66 LD H,(HL)
6f LD L,A
e9 JP 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: |
| z80.a ^= z80.a; |
| break; |
| |
| case 0x3e: |
| z80.a = readbyte(z80.pc++); |
| break; |
| |
| case 0x46: |
| z80.b = readbyte((z80.h<<8) | z80.l); |
| break; |
| |
| |
| |
| case 0xdd: |
| return handle_prefix_dd(); |
| |
| const opcode = readbyte(z80.pc++); |
| |
| switch (opcode) { |
| case 0xaf: |
| z80.a ^= z80.a; |
| break; |
| |
| case 0x3e: |
| z80.a = readbyte(z80.pc++); |
| break; |
| |
| case 0x46: |
| z80.b = readbyte((z80.h<<8) | z80.l); |
| break; |
| |
| |
| |
| case 0xdd: |
| return handle_prefix_dd(); |
| |
| const opcode = readbyte(z80.pc++); |
| |
| switch (opcode) { |
| case 0xaf: |
| z80.a ^= z80.a; |
| break; |
| |
| case 0x3e: |
| z80.a = readbyte(z80.pc++); |
| break; |
| |
| case 0x46: |
| z80.b = readbyte((z80.h<<8) | z80.l); |
| break; |
| |
| |
| |
| case 0xdd: |
| return handle_prefix_dd(); |
| |
| const opcode = readbyte(z80.pc++); |
| |
| switch (opcode) { |
| case 0xaf: |
| z80.a ^= z80.a; |
| break; |
| |
| case 0x3e: |
| z80.a = readbyte(z80.pc++); |
| break; |
| |
| case 0x46: |
| z80.b = readbyte((z80.h<<8) | z80.l); |
| break; |
| |
| |
| |
| case 0xdd: |
| return handle_prefix_dd(); |
| |
| const opcode = readbyte(z80.pc++); |
| |
| switch (opcode) { |
| case 0xaf: |
| z80.a ^= z80.a; |
| break; |
| |
| case 0x3e: |
| z80.a = readbyte(z80.pc++); |
| break; |
| |
| case 0x46: |
| z80.b = readbyte((z80.h<<8) | z80.l); |
| break; |
| |
| |
| |
| case 0xdd: |
| return handle_prefix_dd(); |
| |
| const opcode = readbyte(z80.pc++); |
| |
| switch (opcode) { |
| case 0xaf: |
| z80.a ^= z80.a; |
| break; |
| |
| case 0x3e: |
| z80.a = readbyte(z80.pc++); |
| break; |
| |
| case 0x46: |
| z80.b = readbyte((z80.h<<8) | z80.l); |
| break; |
| |
| |
| |
| case 0xdd: |
| return handle_prefix_dd(); |
| |
| const opcode = readbyte(z80.pc++); |
| |
| switch (opcode) { |
| case 0xaf: |
| z80.a ^= z80.a; |
| break; |
| |
| case 0x3e: |
| z80.a = readbyte(z80.pc++); |
| break; |
| |
| case 0x46: |
| z80.b = readbyte((z80.h<<8) | z80.l); |
| break; |
| |
| |
| |
| case 0xdd: |
| return handle_prefix_dd(); |
| |
… 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] = |
| |
| 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) |
| | hc_add_table[lookup & 0x07] |
| | oflo_add_table[lookup >> 4] |
| | sz53_table[z80.a]; |
| |
| z80.a = result & 0xff; |
| clock += 7; |
| } |
| const sz53_table[256], hc_add_table[8], oflo_add_table[8] = |
| |
| 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) |
| | hc_add_table[lookup & 0x07] |
| | oflo_add_table[lookup >> 4] |
| | sz53_table[z80.a]; |
| |
| z80.a = result & 0xff; |
| clock += 7; |
| } |
| const sz53_table[256], hc_add_table[8], oflo_add_table[8] = |
| |
| 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) |
| | hc_add_table[lookup & 0x07] |
| | oflo_add_table[lookup >> 4] |
| | sz53_table[z80.a]; |
| |
| z80.a = result & 0xff; |
| clock += 7; |
| } |
| const sz53_table[256], hc_add_table[8], oflo_add_table[8] = |
| |
| 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) |
| | hc_add_table[lookup & 0x07] |
| | oflo_add_table[lookup >> 4] |
| | sz53_table[z80.a]; |
| |
| 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), ...]; |
| const pages = [0, 1, 2]; |
| const ram = new Uint8Array(8192); |
| |
| function readByte(address) { |
| const offset = address & 0x3fff; |
| if (address < 0xc000) { |
| const page = (address < 0x400) ? 0 |
| : pages[address >>> 14]; |
| return romBanks[page][offset]; |
| } |
| |
| |
| return ram[offset & 0x1fff]); |
| } |
| const romBanks = [new Uint8Array(16384), ...]; |
| const pages = [0, 1, 2]; |
| const ram = new Uint8Array(8192); |
| |
| function readByte(address) { |
| const offset = address & 0x3fff; |
| if (address < 0xc000) { |
| const page = (address < 0x400) ? 0 |
| : pages[address >>> 14]; |
| return romBanks[page][offset]; |
| } |
| |
| |
| return ram[offset & 0x1fff]); |
| } |
| const romBanks = [new Uint8Array(16384), ...]; |
| const pages = [0, 1, 2]; |
| const ram = new Uint8Array(8192); |
| |
| function readByte(address) { |
| const offset = address & 0x3fff; |
| if (address < 0xc000) { |
| const page = (address < 0x400) ? 0 |
| : pages[address >>> 14]; |
| return romBanks[page][offset]; |
| } |
| |
| |
| return ram[offset & 0x1fff]); |
| } |
| const romBanks = [new Uint8Array(16384), ...]; |
| const pages = [0, 1, 2]; |
| const ram = new Uint8Array(8192); |
| |
| function readByte(address) { |
| const offset = address & 0x3fff; |
| if (address < 0xc000) { |
| const page = (address < 0x400) ? 0 |
| : pages[address >>> 14]; |
| return romBanks[page][offset]; |
| } |
| |
| |
| return ram[offset & 0x1fff]); |
| } |
| const romBanks = [new Uint8Array(16384), ...]; |
| const pages = [0, 1, 2]; |
| const ram = new Uint8Array(8192); |
| |
| function readByte(address) { |
| const offset = address & 0x3fff; |
| if (address < 0xc000) { |
| const page = (address < 0x400) ? 0 |
| : pages[address >>> 14]; |
| return romBanks[page][offset]; |
| } |
| |
| |
| return ram[offset & 0x1fff]); |
| } |
| const romBanks = [new Uint8Array(16384), ...]; |
| const pages = [0, 1, 2]; |
| const ram = new Uint8Array(8192); |
| |
| function readByte(address) { |
| const offset = address & 0x3fff; |
| if (address < 0xc000) { |
| const page = (address < 0x400) ? 0 |
| : pages[address >>> 14]; |
| return romBanks[page][offset]; |
| } |
| |
| |
| return ram[offset & 0x1fff]); |
| } |
Emulating Memory
| function writeByte(address, byte) { |
| if (address < 0xc000) |
| return; |
| |
| if (address === 0xfffc) |
| ; |
| else if (address >= 0xfffd) |
| pages[address - 0xfffd] = byte; |
| else ram[address & 0x1fff] = byte; |
| } |