Emulating the
BBC Micro
in Javascript

Matt Godbolt

Emulating the
BBC Micro
in Javascript

Matt Godbolt

Trying to recapture a lost youth

What's a BBC Micro?

Why BBC Micro?

Why BBC Micro?

Why Javascript?

  • Surely it'd be too slow?
  • "Worse" than C++?
  • Why not?
  • Runs in browser

The 6502

6502 Computers

6502 101

Registers

  • 8-bit Accumulator
  • 8-bit X, Y index
  • Processor status
  • Stack pointer
  • Program counter

Instructions


LDA STA LDX ... ; load / store A, X, Y
TAX TXA ...     ; transfer A to X
PHA PLA         ; push A / pull A
CMP             ; compare with A
ADC SBC         ; add / subtract with carry
CLC SEC         ; clear / set carry
JMP             ; jump
BEQ BNE BCS BCC ; branch ==, !=, carry set/clear
BMI BPL ...     ; <0, >=0
JSR             ; jump to subroutine
RTS             ; return from subroutine
                     

Addressing modes


a9 20     LDA #$20     ; A = 0x20

a5 70     LDA $70      ; A = readmem(0x70)

ad 34 12  LDA $1234    ; A = readmem(0x1234)
                     

Addressing modes


bd 34 12  LDA $1234, X ; A = readmem(0x1234 + X)
b9 34 12  LDA $1234, Y ; A = readmem(0x1234 + Y)

b1 70     LDA ($70), Y ; t1 = readmem(0x70)
                       ; t2 = readmem(0x71)
                       ; addr = t1 | (t2<<8)
                       ; A = readmem(addr + Y)

b5 70     LDA $70, X   ; A = readmem(0x70 + X)
                     

Emulating a 6502

  • Fetch next instruction
  • Decode it
  • Execute it

Emulation


var a = 0, x = 0, y = 0, pc = readword(0xfffe);
while (true) {
  switch (readmem(pc++)) {
case 0xa9: // LDA #imm a = readmem(pc++); break;
case 0xad: // LDA $addr var addr = readword(pc); pc += 2; a = readmem(addr); break;
// and so on for all other instructions... } }

Except...

  • Setting flags
  • Memory access
  • Hardware
  • Interrupts
  • Handling time

Processor status: Flags

7 6 5 4 3 2 1 0
Negative oVerflow - - Decimal Interrupt
disable
Zero Carry

  • Z & N set on ALU/load instruction
  • C set by shifts & arithmetic
  • V set by arithmetic
  • D, V, C and I controlled by special instructions

So now we have:


case 0xa9: // LDA #imm
  a = readmem(pc++);
  p.z = !a;
  p.n = !!(a & 0x80);
  break;             

Memory

0x10000
0xff00 256 bytes OS ROM
0xfe00 256 bytes hardware
0xfd00 256 bytes ROM
0xfc00 256 bytes hardware
↑
↑
↑
0xc000
15.5KB OS ROM
↑
↑
↑
↑
0x8000
16KB Paged ROM
↑
↑
↑
↑
0x4000
16KB RAM
↑
↑
↑
↑
0x0000
16KB RAM

var ram = new Uint8Array(0x8000), romsel = 0, roms = [];
var os = load("OS");
roms[15] = load("BASIC");

function readmem(addr) {
  if (addr < 0x8000) return ram[addr];
  if (addr < 0xc000) return roms[romsel][addr - 0x8000];
  if ((addr >= 0xfe00 && addr < 0xff00)
      || (addr >= 0xfc00 && addr < 0xfd00))
    return readhw(addr);
  return os[addr - 0xc000];
}
                         

function writemem(addr, b) {
    if (addr < 0x8000) ram[addr] = b;
    if (addr >= 0xfe00 && addr < 0xff00) writehw(addr, b);
    // else does nothing - it's ROM
}
                         

The outside world

  • Video chip:
    • Pixel generator reads from RAM
    • Simple character-mapped mode
    • Bitmap modes with 2, 4, 16 colours
  • Sound (SN76489)
  • Timers (6522 VIA)

Demo

What happened?

Fixing it


case 0xa9: // LDA #imm
a = readmem(pc++); p.z = !a; p.n = !!(a & 0x80);
video.run(2);
timers.run(2);
break;

Demo

More games

Zalaga

Inside the 6502

    $84 10000100 STY zp
    $85 10000101 STA zp
    $86 10000110 STX zp
    $87 10000111 ???

Real decoding

©2010 Visual6502.org, used with permission

Inside the 6502

                 +-----+-----+-----+
                 |  A  |  X  |  Y  |
                 +-----+-----+-----+
                    |     |     |
        /+--------- | --- | (!s && !t)
        ||          |     |     |
+----------+        |     |     |
| opcode|| |        |     |     |
| 100001st |------->t     |     |
+----------+        |     |     |
     ^  \---------- | --->s     |
     |              \     |     /
     |               \    |    /
  select              write bus
   store
            

Inside the 6502

                 +-----+-----+-----+
                 |  A  |  X  |  Y  |
                 +-----+-----+-----+
                    |     |     |
        /+--------- | --- | (!s && !t)
        ||          |     |     |
+----------+        |     |     |
| opcode|| |        |     |     |
| 100001st |------->t     |     |
+----------+        |     |     |
     ^  \---------- | --->s     |
     |              \     |     /
     |               \    |    /
  select              write bus
   store
            

Inside the 6502

$84 10000100 STY zp
$85 10000101 STA zp
$86 10000110 STX zp
$87 10000111 SAX zp

Even more games

An admission

Protection systems

  • Decrypt game code
  • Keys include:
    • Magic values loaded from tape (or disc)
    • Decryption code itself
    • Hardware timers & registers
    • Interrupt information
    • Read-modify-write

Accurate timing information

Visual 6502

ROL $fe48

Cycle Read / Write Address Description
0 Read 0d2d Read opcode (2e)
1 Read 0d2e Read low byte of address (48)
2 Read 0d2f Read high byte of address (fe)
3 Read fe48 Read memory at $fe48
4 Write fe48 Do rotate — writes unmodified value!
5 Write fe48 Store to $fe48

Fixing it


case 0x2e: // ROL abs
addr = readword(pc); pc += 2; hardware.run(3);
val = readbyte(addr); hardware.run(1);
writebyte(addr, val); // unmodified value hardware.run(1);
val = (val << 1) | (carry ? 1); carry = val & 0x100;
writebyte(addr, val & 0xff); // modified value hardware.run(1);
break;

Cycle stretching

Conclusion

Resources

Some Demos