ruby2600 - an Atari 2600 emulator written in Ruby

Preview:

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

An Atari 2600 emulator100% written in Ruby

(and RSpec!)

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

If so, you probably had this...

...or one of these...

...or you used an emulator

http://stella.sourceforge.net

Emulator

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

another type by simulating the hardware of the original system

ruby2600

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

http://github.com/chesterbr/ruby2600

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? ☺)

Work in progress!

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

http://github.com/chesterbr/ruby2600

We'll see

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

@chesterbrhttp://chester.me

About me (Chester)

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

Building an Emulator:

How the Atari 2600 works

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

Cartridge connector

CPU: 6507

Video: TIA

Everything else: RIOT (6532)

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)

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

Building an Emulator:

CPU

image CC-BY Konstantin Lanzet

65xx family: in the past...

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

...and in the future!

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

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

Emulated 6507

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

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

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

...

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!

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

6507 Instruction Set, 1/2

6507 Instruction Set, 2/2

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 ...

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

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

“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

Less effort → better coverage

● Each instruction tested in everypossible addressing mode

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

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

...

def step fetch decode execute

return @time_in_cycles end

CPU.rb

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

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

CPU.rb

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

Final result

Full instruction set coveredby more than 1,700 tests

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

Building an Emulator:Building an Emulator:

ArchitectureArchitecture

image CC-BY image CC-BY Benjamin EshanBenjamin Eshan

ruby2600 class diagram

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)

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)

ruby2600 class diagram

Ruby spice: duck typing

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

Cart classes “quack” like arrays

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

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.)

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

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

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

BALL

PLAYFIELD

PLAYERS (2)

MISSILES (2)

Graphic Objects

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?

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

ruby2600 class diagram

Building an Emulator:

Let's run it!

reproduction: reproduction: Young FrankensteinYoung Frankenstein

Building an Emulator:

Speeding up

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

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"

Refactoring

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

(while keeping it readable)

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

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

Questions?Thank you!

@chesterbrhttp://slideshare.net/chesterbr

http://github.com/chesterbr/ruby2600

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.

Recommended