Hi,

here is my solution:

I initialize the registers with zeroes, because it simplifies unit- 
testing.

A binary program is unpacked into an array of opcodes (16bit). The
program counter (named "current") counts opcodes, not bytes, so
for a jump instruction the destination address has to be divided by two.

I also wrote a small "disassembler" which dumped the test program as:

000: [6177] V1 = 0x77
002: [6245] V2 = 0x45
004: [7101] V1 = V1 + 0x01
006: [8320] V3 = V2
008: [8121] V1 = V1 | V2
00A: [8122] V1 = V1 & V2
00C: [8233] V2 = V2 ^ V3
00E: [8134] V1 = V1 + V3
010: [8235] V2 = V2 - V3
012: [8106] V1 = V1 >> 1
014: [8327] V3 = V2 - V3
016: [830E] V3 = V3 << 1
018: [64FF] V4 = 0xFF
01A: [C411] V4 = rand() & 0x11
01C: [32BB] skip next if V2 == 0xBB
01E: [1000] goto 000
020: [0000] exit

(Almost looks like ruby code. We really need a "goto" in ruby ;-)

Regards,
Boris



### chip8_emu.rb
class Chip8Emulator
   attr_accessor :register

   VF = 15 # index of the carry/borrow register

   def initialize
     @register = [0] * 16 # V0..VF
   end

   def exec(program)
     opcodes = program.unpack('n*')
     current = 0 # current opcode index
     loop do
       opcode = opcodes[current]

       # these are needed often:
       x   = opcode >> 8 & 0x0F
       y   = opcode >> 4 & 0x0F
       kk  = opcode & 0xFF
       nnn = opcode & 0x0FFF

       case opcode >> 12 # first nibble
       when 0 then return
       when 1 then current = (nnn) / 2 and next
       when 3 then current += 1 if @register[x] == kk
       when 6 then @register[x] = kk
       when 7 then add(x, kk)
       when 8
         case opcode & 0x0F # last nibble
         when 0 then @register[x]  = @register[y]
         when 1 then @register[x] |= @register[y]
         when 2 then @register[x] &= @register[y]
         when 3 then @register[x] ^= @register[y]
         when 4 then add(x, @register[y])
         when 5 then subtract(x, x, y)
         when 6 then shift_right(x)
         when 7 then subtract(x, y, x)
         when 0xE then shift_left(x)
         else raise "Unknown opcode: " + opcode.to_s(16)
         end
       when 0xC then random(x, kk)
       else raise "Unknown opcode: " + opcode.to_s(16)
       end
       current += 1 # next opcode
     end
   end

   def add(reg, value)
     result = @register[reg] + value
     @register[reg] = result & 0xFF
     @register[VF]  = result >> 8 # carry
   end

   def subtract(reg, a, b)
     result = @register[a] - @register[b]
     @register[reg] = result & 0xFF
     @register[VF]  = - (result >> 8) # borrow
   end

   def shift_right(reg)
     @register[VF] = @register[reg] & 0x01
     @register[reg] >>= 1
   end

   def shift_left(reg)
     @register[VF] = @register[reg] >> 7
     @register[reg] = (@register[reg] << 1) & 0xFF
   end

   def random(reg, kk)
     @register[reg] = rand(256) & kk
   end

   # show all registers
   def dump
     0.upto(VF) do |reg|
       printf("V%1X:%08b\n", reg, @register[reg])
     end
   end
end

if $0 == __FILE__
   ARGV.each do |program|
     emu = Chip8Emulator.new
     emu.exec(File.read(program))
     emu.dump
   end
end




### test_chip8_emu.rb
require 'test/unit'
require 'chip8_emu'

class Chip8EmulatorTest < Test::Unit::TestCase
   def setup
     @emu = Chip8Emulator.new
   end

   def test_init
     assert_equal [0] * 16, @emu.register
   end

   def test_set_register
     @emu.exec("\x60\x42" + "\x63\xFF" + "\x6F\x66" + "\0\0")
     assert_equal [66, 0, 0, 255] + [0]*11 + [102], @emu.register
   end

   def test_jump
     @emu.exec("\x10\x04" + "\x00\x00" + "\x60\x42" + "\0\0")
     assert_equal [66] + [0]*15, @emu.register
   end

   def test_skip_next
     @emu.exec("\x60\x42" + "\x30\x42" + "\x60\x43" + "\0\0")
     assert_equal [66] + [0]*15, @emu.register
   end

   def test_add_const
     @emu.exec("\x60\xFF" + "\x70\x01" + "\0\0")
     assert_equal [0]*15 + [1], @emu.register
   end

   def test_copy
     @emu.exec("\x60\x42" + "\x81\x00" + "\0\0")
     assert_equal [66]*2 + [0]*14, @emu.register
   end

   def test_or
     @emu.exec("\x60\x03" + "\x61\x05" + "\x80\x11" + "\0\0")
     assert_equal [7, 5] + [0]*14, @emu.register
   end

   def test_and
     @emu.exec("\x60\x03" + "\x61\x05" + "\x80\x12" + "\0\0")
     assert_equal [1, 5] + [0]*14, @emu.register
   end

   def test_xor
     @emu.exec("\x60\x03" + "\x61\x05" + "\x80\x13" + "\0\0")
     assert_equal [6, 5] + [0]*14, @emu.register
   end

   def test_add
     @emu.exec("\x60\x01" + "\x61\x01" + "\x80\x14" + "\0\0")
     assert_equal [2, 1] + [0]*14, @emu.register
   end

   def test_subtract
     @emu.exec("\x60\x00" + "\x61\x01" + "\x80\x15" + "\0\0")
     assert_equal [255, 1] + [0]*13 + [1], @emu.register
   end

   def test_subtract2
     @emu.exec("\x60\x01" + "\x61\x02" + "\x80\x17" + "\0\0")
     assert_equal [1, 2] + [0]*14, @emu.register
   end

   def test_shift_right
     @emu.exec("\x60\xFF" + "\x80\x06" + "\0\0")
     assert_equal [0x7F] + [0]*14 + [1], @emu.register
   end

   def test_shift_left
     @emu.exec("\x60\xFF" + "\x80\x0E" + "\0\0")
     assert_equal [0xFE] + [0]*14 + [1], @emu.register
   end

   def test_rand
     srand 0
     first_rand = rand(256)
     srand 0
     @emu.exec("\xC0\x0F" + "\0\0")
     assert_equal [first_rand & 0x0F] + [0]*15, @emu.register
   end
end




### chip8_asm.rb
class Chip8Disassembler
   CODES = {
     /0000/     => 'exit',
     /1(...)/   => 'goto \1',
     /3(.)(..)/ => 'skip next if V\1 == 0x\2',
     /6(.)(..)/ => 'V\1 = 0x\2',
     /7(.)(..)/ => 'V\1 = V\1 + 0x\2',
     /8(.)(.)0/ => 'V\1 = V\2',
     /8(.)(.)1/ => 'V\1 = V\1 | V\2',
     /8(.)(.)2/ => 'V\1 = V\1 & V\2',
     /8(.)(.)3/ => 'V\1 = V\1 ^ V\2',
     /8(.)(.)4/ => 'V\1 = V\1 + V\2',
     /8(.)(.)5/ => 'V\1 = V\1 - V\2',
     /8(.)06/   => 'V\1 = V\1 >> 1',
     /8(.)(.)7/ => 'V\1 = V\2 - V\1',
     /8(.)0E/   => 'V\1 = V\1 << 1',
     /C(.)(..)/ => 'V\1 = rand() & 0x\2',
   }

   def self.code2text hexcode
     CODES.each do |re, subs|
       if hexcode =~ re
         return hexcode.sub(re, subs)
       end
     end
     '???'
   end

   def self.dump binary
     opcodes = binary.unpack "n*"
     opcodes.each_with_index do |code, waddr|
       code_hex = "%04X" % code
       printf("%03X: [%s] %s\n", waddr*2, code_hex, code2text 
(code_hex));
     end
   end
end

binary = File.read(ARGV[0])
Chip8Disassembler.dump(binary)