A Beeb in your browser

Matt Godbolt

ABUG 6th June 2020

A Beeb in your browser

Matt Godbolt

Trying to recapture a lost youth

Who am I?

Why the Beeb?

Why the Beeb?

Why a Browser?

jsbeeb

Features

  • Fullscreen
  • Printer
  • Freeze / Speedy
  • Debugger

Debugging

  • Registers
  • Breakpoints
  • Watchpoints
  • Traces

Debugging

  • j/k; u/i; U/I
  • n/m/o
  • t - breakpoint

URLs

Just in!

"Native" jsbeeb

Emulating!

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 chips (6845, Video ULA, SAA5050)
  • Sound (SN76489)
  • Timers (6522 VIA)

Putting it together

  • Run CPU for 1/60th second
  • Paint the screen!
  • 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