67

Click here to load reader

ruby2600 - an Atari 2600 emulator written in Ruby

Embed Size (px)

DESCRIPTION

The emulator was presented to the public at RubyConfBr 2013. Its source code can be downloaded at http://github.com/chesterbr/ruby2600 The video is on YouTube: http://www.youtube.com/watch?v=S3qAOu41CxE

Citation preview

Page 1: ruby2600 - an Atari 2600 emulator written in Ruby

An Atari 2600 emulator100% written in Ruby

(and RSpec!)

Carlos Duarte do Nascimento (Chester)@chesterbr / http://chester.me

Page 2: ruby2600 - an Atari 2600 emulator written in Ruby
Page 3: ruby2600 - an Atari 2600 emulator written in Ruby

If so, you probably had this...

Page 4: ruby2600 - an Atari 2600 emulator written in Ruby

...or one of these...

Page 5: ruby2600 - an Atari 2600 emulator written in Ruby

...or you used an emulator

http://stella.sourceforge.net

Page 6: ruby2600 - an Atari 2600 emulator written in Ruby

Emulator

A program that runs software written for one type of computer system in

another type by simulating the hardware of the original system

Page 7: ruby2600 - an Atari 2600 emulator written in Ruby

ruby2600

● Atari 2600 emulator● Written in Ruby● Runs quite a few classic games● Open-source

http://github.com/chesterbr/ruby2600

Page 8: ruby2600 - an Atari 2600 emulator written in Ruby

Why?

There are great emulators out there, but they strive for speed above readability

A test-driven emulator in a high-level language is a great learning tool

Always wondered how much TDD would help on wildly unfamiliar territory

(also: why not? ☺)

Page 9: ruby2600 - an Atari 2600 emulator written in Ruby

Work in progress!

● A few subtle bugs● Does not run every game● Not full-speed● No sound

http://github.com/chesterbr/ruby2600

Page 10: ruby2600 - an Atari 2600 emulator written in Ruby

We'll see

● How the Atari works● CPU emulation● Architecture (just a bit) ● Ruby2600 in action● The future

Page 11: ruby2600 - an Atari 2600 emulator written in Ruby

@chesterbrhttp://chester.me

About me (Chester)

© Ila Fox - http://www.ilafox.com

Page 12: ruby2600 - an Atari 2600 emulator written in Ruby

Building an Emulator:

How the Atari 2600 works

Page 13: ruby2600 - an Atari 2600 emulator written in Ruby

Let's peek inside...(Atari 2600 Jr. printed circuit board)

Page 14: ruby2600 - an Atari 2600 emulator written in Ruby

Cartridge connector

Page 15: ruby2600 - an Atari 2600 emulator written in Ruby

CPU: 6507

Page 16: ruby2600 - an Atari 2600 emulator written in Ruby

Video: TIA

Page 17: ruby2600 - an Atari 2600 emulator written in Ruby

Everything else: RIOT (6532)

Page 18: ruby2600 - an Atari 2600 emulator written in Ruby

Challenging specs● CPU speed: 1.19 MHz (not GHz)● Max cart (program) size: 4 KB● RAM: 128 bytes● Video RAM: 0KB (game code has to drive TIA into generating each scanline in realtime)

Page 19: ruby2600 - an Atari 2600 emulator written in Ruby

Atari 2600 in a nutshell

The CPU reads a game program from the ROM chip on the cartrigde

It confgures the pixels generated by TIA, using RAM, I/O and timers

provided by the RIOT chip

Our goal: simulate this in software

Page 20: ruby2600 - an Atari 2600 emulator written in Ruby

Building an Emulator:

CPU

image CC-BY Konstantin Lanzet

Page 21: ruby2600 - an Atari 2600 emulator written in Ruby

65xx family: in the past...

http://en.wikipedia.org/wiki/MOS_Technology_6502#Computers_and_games

Page 22: ruby2600 - an Atari 2600 emulator written in Ruby

...and in the future!

http://en.wikipedia.org/wiki/MOS_Technology_6502#In_popular_culture

Page 23: ruby2600 - an Atari 2600 emulator written in Ruby

The 6507 CPU

Reads instructions from the cartridge that manipulate and transfer bytes between chips, keeping state on

internal registers and flags

http://en.wikipedia.org/wiki/Von_Neumann_architecture

Page 24: ruby2600 - an Atari 2600 emulator written in Ruby

Emulated 6507

As it executes each instruction, it keeps instance variables for registers

(@a, @x, @y), flags (@n, @z, ...) and @memory as an array

Page 25: ruby2600 - an Atari 2600 emulator written in Ruby

CPU.rb

module Ruby2600 class CPU attr_accessor :memory attr_accessor :pc, :a, :x, :y, :s

# Flags (P register): nv--dizc attr_accessor :n, :v, :d, :i, :z, :c

def step ...runs a instruction... end

...

Page 26: ruby2600 - an Atari 2600 emulator written in Ruby

module Ruby2600 class CPU attr_accessor :memory attr_accessor :pc, :a, :x, :y, :s

# Flags (P register): nv--dizc attr_accessor :n, :v, :d, :i, :z, :c

def step ...runs a instruction... end

...

CPU.rb

TESTS FIRST!TESTS FIRST!

Page 27: ruby2600 - an Atari 2600 emulator written in Ruby
Page 28: ruby2600 - an Atari 2600 emulator written in Ruby

Assembly debugging == PAIN!

To avoid it, we need technical specifcations

that are easy to read, yet detail-oriented enough to

test emulator code

RSpec does the job!

http://rspec.info

Page 29: ruby2600 - an Atari 2600 emulator written in Ruby

6507 Instruction Set, 1/2

Page 30: ruby2600 - an Atari 2600 emulator written in Ruby

6507 Instruction Set, 2/2

Page 31: ruby2600 - an Atari 2600 emulator written in Ruby

CPU_spec.rb context 'INX' do before do cpu.memory[0] = 0xE8 # INX cpu.pc = 0x0000 cpu.x = 0x07 end

it 'should advance PC by one' do cpu.step cpu.pc.should == 0x0001 end

it 'should set X value' do cpu.step cpu.x.should == 0x08 end ...

Page 32: ruby2600 - an Atari 2600 emulator written in Ruby

Using shared examplesshared_examples_for 'advance PC by one' do it { expect { cpu.step }.to change { cpu.pc }.by 1 }end

shared_examples_for 'set X value' do |expected| it do cpu.step

value = cpu.x value.should be(expected), "Expected: #{hex_bye(expected)}, " + "found: #{hex_byte(value)}" endend

Page 33: ruby2600 - an Atari 2600 emulator written in Ruby

More syntactic sugar1.upto 3 do |number| shared_examples_for "advance PC by #{number.humanize}" do it { expect { cpu.step }.to change { cpu.pc }.by number } endend

RSpec.configure do |c| c.alias_it_should_behave_like_to :it_should, 'should'end

Page 34: ruby2600 - an Atari 2600 emulator written in Ruby

“Literate Testing”

context 'INX' do before do cpu.memory[0] = 0xE8 # INX cpu.x = 0x07 end

it_should 'advance PC by one' it_should 'take two cycles' it_should 'set X value', 0x08 it_should 'reset Z flag' it_should 'reset N flag' ...

http://en.wikipedia.org/wiki/Literate_programming

Page 35: ruby2600 - an Atari 2600 emulator written in Ruby

Less effort → better coverage

● Each instruction tested in everypossible addressing mode

● Easy to understand, high-level tests● Tests become a specifcation - specs!

Page 36: ruby2600 - an Atari 2600 emulator written in Ruby

CPU.rb

module Ruby2600 class CPU attr_accessor :memory attr_accessor :pc, :a, :x, :y, :s

# Flags (P register): nv--dizc attr_accessor :n, :v, :d, :i, :z, :c

def step ...runs a instruction... end

...

Page 37: ruby2600 - an Atari 2600 emulator written in Ruby
Page 38: ruby2600 - an Atari 2600 emulator written in Ruby

def step fetch decode execute

return @time_in_cycles end

CPU.rb

Page 39: ruby2600 - an Atari 2600 emulator written in Ruby

No byte/worddata types :-( CPU.rb

def fetch @opcode = memory[@pc] @param_lo = memory[word(@pc + 1)] @param_hi = memory[word(@pc + 2)] @param = @param_hi * 0x100 + @param_lo @pc = word(@pc + OPCODE_SIZES[@opcode]) end

Lookup table

Page 40: ruby2600 - an Atari 2600 emulator written in Ruby

morelookup!

CPU.rb def decode if (@opcode & 0b11111) == BXX @instruction = BXX elsif (@opcode & 0b11111) == SCX @instruction = SCX else @instruction_group = @opcode & 0b11 mode_in_group = (@opcode & 0b11100) >> 2 @addressing_mode = ADDRESSING[ @instruction_group][mode_in_group] @instruction = @opcode & 0b11100011 end

@time_in_cycles = time_in_cycles end

Page 41: ruby2600 - an Atari 2600 emulator written in Ruby

CPU.rb

def execute case @instruction when LDA flag_nz @a = load when STA store @a when JMPabs @pc = @param when BXX @pc = branch ...

Page 42: ruby2600 - an Atari 2600 emulator written in Ruby

Final result

Full instruction set coveredby more than 1,700 tests

Only one CPU bug so farhad to be debugged in-game

Page 43: ruby2600 - an Atari 2600 emulator written in Ruby

Building an Emulator:Building an Emulator:

ArchitectureArchitecture

image CC-BY image CC-BY Benjamin EshanBenjamin Eshan

Page 44: ruby2600 - an Atari 2600 emulator written in Ruby

ruby2600 class diagram

Page 45: ruby2600 - an Atari 2600 emulator written in Ruby

Memory-based I/O

CPU “talks” to other chips by reading and writing specifc memory locations:

0000-002C – TIA (write)0030-003D – TIA (read)0080-00FF – RIOT (RAM)0280-0297 – RIOT (I/O,Timer)F000-FFFF – Cartridge (ROM)

(very simplified, see: http://nocash.emubase.de/2k6specs.htm)

Page 46: ruby2600 - an Atari 2600 emulator written in Ruby

Bus

Acts as a memory façade to theCPU, routing reads and writesto the appropriate chip class

It is also the interface where we “plug” an UI (anything that displays

images and reads keypresses)

Page 47: ruby2600 - an Atari 2600 emulator written in Ruby

ruby2600 class diagram

Page 48: ruby2600 - an Atari 2600 emulator written in Ruby

Ruby spice: duck typing

Instead of defning read/write methods, make Bus, TIA, RIOT and

Cart classes “quack” like arrays

Page 49: ruby2600 - an Atari 2600 emulator written in Ruby

TIA.rb / RIOT.rb / Cart.rb(and also bus.rb!)

def [](position) ...return value for position... end

def []=(position, value) ...react to writing value to position... end

Page 50: ruby2600 - an Atari 2600 emulator written in Ruby

Benefts

● High decoupling: CPU, TIA, RIOT and Cart are (mostly) independent

● We can use regular arrays as mocks for TIA, RIOT, Cart and Bus itself!

● We can “plug” different UIs (e.g.: text-mode rendering, network multiplayer, joystick interface, etc.)

Page 51: ruby2600 - an Atari 2600 emulator written in Ruby

Cart.rb (full source)class Cart def initialize(rom_file) @bytes = File.open(rom_file, "rb") { |f| f.read }.unpack('C*') @bytes += @bytes if @bytes.count == 2048 end

def [](address) @bytes[address] end

def []=(address, value) # Don't write to Read-Only Memory, duh! endend

Page 52: ruby2600 - an Atari 2600 emulator written in Ruby

TimingAs TIA generates each pixel for each scanline, it will "tick" the CPU and RIOT to keep everything in sync

image cc-by Steve Evans

Page 53: ruby2600 - an Atari 2600 emulator written in Ruby

TIA.rbdef draw_scanline scanline = [] 0.upto(SCANLINE_WIDTH) do |pixel| scanline << topmost_pixel tick_other_chips pixel end return scanlineend

def topmost_pixel ...

def tick_other_chips @cpu.tick if pixel % 3 == 2 @riot.tick if pixel % 3 == 0end

TIA runs 3x fasterthan CPU and RIOT

Page 54: ruby2600 - an Atari 2600 emulator written in Ruby

BALL

PLAYFIELD

PLAYERS (2)

MISSILES (2)

Graphic Objects

Page 55: ruby2600 - an Atari 2600 emulator written in Ruby

Graphic Objects

To keep TIA's responsibility focused on building the frames, we will offload

object drawing to separate classes (Playfeld, Player, Ball, Missile)

For the common behavior, should we use composition or inheritance?

Page 56: ruby2600 - an Atari 2600 emulator written in Ruby

Composition and InheritanceA common ancestor (Graphic)

contains the common behavior, and each class adds its own flavor

Behavior that does not defne a Graphic (position counters) is better

suited for a separate class, using composition instead of inheritance

Page 57: ruby2600 - an Atari 2600 emulator written in Ruby

ruby2600 class diagram

Page 58: ruby2600 - an Atari 2600 emulator written in Ruby

Building an Emulator:

Let's run it!

reproduction: reproduction: Young FrankensteinYoung Frankenstein

Page 59: ruby2600 - an Atari 2600 emulator written in Ruby
Page 60: ruby2600 - an Atari 2600 emulator written in Ruby

Building an Emulator:

Speeding up

TBBT 1-06 "The Middle-Earth Paradigm", © 2007 Chuck Lorre Productions / WB Television

Page 61: ruby2600 - an Atari 2600 emulator written in Ruby

Knuth, Donald (December 1974). "Structured Programmingwith go

to Statements", ACM Journal

Me and Prof. Knuth hanging out. We're, like, bros. Really.

"We should forget about small

efficiencies, say about 97% of the time: premature

optimization is the root of all evil"

Page 62: ruby2600 - an Atari 2600 emulator written in Ruby

Refactoring

Thanks to test coverage, we can safely play around and refactor for speed

(while keeping it readable)

Page 63: ruby2600 - an Atari 2600 emulator written in Ruby

JRuby

We have a small working set (no more than 5KB) handled in very tight loops

Have to measure, but it smells like the kind of modern-CPU-friendly code

that could be generated via JIT

http://en.wikipedia.org/wiki/Just-in-time_compilation

Page 64: ruby2600 - an Atari 2600 emulator written in Ruby

If nothing else works

The modular design gives us freedom to rewrite any part using other languages and/or replace them with existing code

http://www.6502.org/tools/emu/#emulation

Page 66: ruby2600 - an Atari 2600 emulator written in Ruby

Questions?Thank you!

@chesterbrhttp://slideshare.net/chesterbr

http://github.com/chesterbr/ruby2600

Page 67: ruby2600 - an Atari 2600 emulator written in Ruby

Credits and LicenseThis presentation is available under the

Creative Commons “by-nc” 3.0 licensenoticing the exceptions below

Images from third parties were included (with due credits) underfair use assumption and/or under their respective licenses.

These are excluded from the license above.

Atari™ and likewise characters/games/systems are mentioned uniquely for illustrative purposes under fair use assumption. They are property of

their rights holders, and are also excluded from the license above.